diff --git a/features/step_definitions/cli_steps.ts b/features/step_definitions/cli_steps.ts index 02843d5ab..34639d27a 100644 --- a/features/step_definitions/cli_steps.ts +++ b/features/step_definitions/cli_steps.ts @@ -1,4 +1,4 @@ -import { When, Then } from '../../' +import { When, Then, DataTable } from '../../' import { expect } from 'chai' import { normalizeText } from '../support/helpers' import stringArgv from 'string-argv' @@ -39,7 +39,7 @@ When( When( /^I run cucumber-js \(installed (locally|globally)\)$/, { timeout: 10000 }, - async function(this: World, location) { + async function(this: World, location: string) { if (location === 'locally') { return this.run(this.localExecutablePath, []) } @@ -57,19 +57,22 @@ Then(/^it fails$/, function(this: World) { this.verifiedLastRunError = true }) -Then(/^it outputs the text:$/, function(this: World, text) { +Then(/^it outputs the text:$/, function(this: World, text: string) { const actualOutput = normalizeText(this.lastRun.output) const expectedOutput = normalizeText(text) expect(actualOutput).to.eql(expectedOutput) }) -Then(/^the output contains the text:$/, function(this: World, text) { +Then(/^the output contains the text:$/, function(this: World, text: string) { const actualOutput = normalizeText(this.lastRun.output) const expectedOutput = normalizeText(text) expect(actualOutput).to.include(expectedOutput) }) -Then('the output does not contain the text:', function(this: World, text) { +Then('the output does not contain the text:', function( + this: World, + text: string +) { const actualOutput = normalizeText(this.lastRun.output) const expectedOutput = normalizeText(text) expect(actualOutput).not.to.include(expectedOutput) @@ -77,7 +80,7 @@ Then('the output does not contain the text:', function(this: World, text) { Then(/^the error output contains the text snippets:$/, function( this: World, - table + table: DataTable ) { const actualOutput = normalizeText(this.lastRun.errorOutput) table.rows().forEach(row => { @@ -86,7 +89,10 @@ Then(/^the error output contains the text snippets:$/, function( }) }) -Then(/^the error output contains the text:$/, function(this: World, text) { +Then(/^the error output contains the text:$/, function( + this: World, + text: string +) { const actualOutput = normalizeText(this.lastRun.errorOutput) const expectedOutput = normalizeText(text) expect(actualOutput).to.include(expectedOutput) diff --git a/features/step_definitions/file_steps.ts b/features/step_definitions/file_steps.ts index 6b57f1d71..e7c4ea5d9 100644 --- a/features/step_definitions/file_steps.ts +++ b/features/step_definitions/file_steps.ts @@ -7,37 +7,46 @@ import path from 'path' import Mustache from 'mustache' import { World } from '../support/world' -Given(/^a file named "(.*)" with:$/, function( +Given(/^a file named "(.*)" with:$/, async function( this: World, - filePath, - fileContent + filePath: string, + fileContent: string ) { const absoluteFilePath = path.join(this.tmpDir, filePath) if (filePath === '@rerun.txt') { fileContent = fileContent.replace(/\//g, path.sep) } - return fsExtra.outputFile(absoluteFilePath, fileContent) + await fsExtra.outputFile(absoluteFilePath, fileContent) }) -Given(/^an empty file named "(.*)"$/, function(this: World, filePath) { +Given(/^an empty file named "(.*)"$/, async function( + this: World, + filePath: string +) { const absoluteFilePath = path.join(this.tmpDir, filePath) - return fsExtra.outputFile(absoluteFilePath, '') + await fsExtra.outputFile(absoluteFilePath, '') }) -Given(/^a directory named "(.*)"$/, function(this: World, filePath) { +Given(/^a directory named "(.*)"$/, async function( + this: World, + filePath: string +) { const absoluteFilePath = path.join(this.tmpDir, filePath) - return fsExtra.mkdirp(absoluteFilePath) + await fsExtra.mkdirp(absoluteFilePath) }) -Given(/^"([^"]*)" is an absolute path$/, function(this: World, filePath) { +Given(/^"([^"]*)" is an absolute path$/, function( + this: World, + filePath: string +) { filePath = Mustache.render(filePath, this) expect(path.isAbsolute(filePath)).to.eql(true) }) Then(/^the file "([^"]*)" has the text:$/, async function( this: World, - filePath, - text + filePath: string, + text: string ) { filePath = Mustache.render(filePath, this) const absoluteFilePath = path.resolve(this.tmpDir, filePath) diff --git a/features/step_definitions/fixture_steps.ts b/features/step_definitions/fixture_steps.ts index 1bb6f129b..025913dd2 100644 --- a/features/step_definitions/fixture_steps.ts +++ b/features/step_definitions/fixture_steps.ts @@ -6,17 +6,16 @@ import { } from '../support/formatter_output_helpers' import fs from 'mz/fs' import path from 'path' -import { World } from '../support/world' import { messages } from 'cucumber-messages' +import { World } from '../support/world' +import Envelope = messages.Envelope Then( 'the {string} formatter output matches the fixture {string}', async function(this: World, formatter: string, filePath: string) { let actual: any if (formatter === 'message') { - actual = this.lastRun.envelopes.map(e => - (e as messages.Envelope).toJSON() - ) + actual = this.lastRun.envelopes.map((e: Envelope) => e.toJSON()) actual = normalizeMessageOutput(actual, this.tmpDir) } else { const actualPath = path.join(this.tmpDir, `${formatter}.out`) diff --git a/features/step_definitions/message_steps.ts b/features/step_definitions/message_steps.ts index 237d6c546..b35d6a521 100644 --- a/features/step_definitions/message_steps.ts +++ b/features/step_definitions/message_steps.ts @@ -3,19 +3,28 @@ import { Then } from '../../' import { expect } from 'chai' import DataTable from '../../src/models/data_table' import { - getPickleStep, getPickleNamesInOrderOfExecution, + getPickleStep, getTestCaseResult, - getTestStepAttachmentsForStep, getTestStepAttachmentsForHook, + getTestStepAttachmentsForStep, getTestStepResults, } from '../support/message_helpers' import { messages } from 'cucumber-messages' import { World } from '../support/world' +type StringifiedStatus = + | 'UNKNOWN' + | 'PASSED' + | 'SKIPPED' + | 'PENDING' + | 'UNDEFINED' + | 'AMBIGUOUS' + | 'FAILED' + const { Status } = messages.TestResult -Then('it runs {int} scenarios', function(this: World, expectedCount) { +Then('it runs {int} scenarios', function(this: World, expectedCount: number) { const testCaseStartedEvents = _.filter( this.lastRun.envelopes, e => e.testCaseStarted @@ -23,21 +32,21 @@ Then('it runs {int} scenarios', function(this: World, expectedCount) { expect(testCaseStartedEvents).to.have.lengthOf(expectedCount) }) -Then('it runs the scenario {string}', function(this: World, name) { +Then('it runs the scenario {string}', function(this: World, name: string) { const actualNames = getPickleNamesInOrderOfExecution(this.lastRun.envelopes) expect(actualNames).to.eql([name]) }) Then('it runs the scenarios {string} and {string}', function( this: World, - name1, - name2 + name1: string, + name2: string ) { const actualNames = getPickleNamesInOrderOfExecution(this.lastRun.envelopes) expect(actualNames).to.eql([name1, name2]) }) -Then('it runs the scenarios:', function(this: World, table) { +Then('it runs the scenarios:', function(this: World, table: DataTable) { const expectedNames = table.rows().map(row => row[0]) const actualNames = getPickleNamesInOrderOfExecution(this.lastRun.envelopes) expect(expectedNames).to.eql(actualNames) @@ -45,17 +54,19 @@ Then('it runs the scenarios:', function(this: World, table) { Then('scenario {string} has status {string}', function( this: World, - name, - status + name: string, + status: string ) { const result = getTestCaseResult(this.lastRun.envelopes, name) - expect(result.status).to.eql(Status[status.toUpperCase()]) + expect(result.status).to.eql( + Status[status.toUpperCase() as StringifiedStatus] + ) }) Then('the scenario {string} has the steps:', function( this: World, - name, - table + name: string, + table: DataTable ) { const actualTexts = getTestStepResults(this.lastRun.envelopes, name).map( s => s.text @@ -66,44 +77,56 @@ Then('the scenario {string} has the steps:', function( Then('scenario {string} step {string} has status {string}', function( this: World, - pickleName, - stepText, - status + pickleName: string, + stepText: string, + status: string ) { const testStepResults = getTestStepResults(this.lastRun.envelopes, pickleName) const testStepResult = _.find(testStepResults, ['text', stepText]) - expect(testStepResult.result.status).to.eql(Status[status.toUpperCase()]) + expect(testStepResult.result.status).to.eql( + Status[status.toUpperCase() as StringifiedStatus] + ) }) Then( 'scenario {string} attempt {int} step {string} has status {string}', - function(this: World, pickleName, attempt, stepText, status) { + function( + this: World, + pickleName: string, + attempt: number, + stepText: string, + status: string + ) { const testStepResults = getTestStepResults( this.lastRun.envelopes, pickleName, attempt ) const testStepResult = _.find(testStepResults, ['text', stepText]) - expect(testStepResult.result.status).to.eql(Status[status.toUpperCase()]) + expect(testStepResult.result.status).to.eql( + Status[status.toUpperCase() as StringifiedStatus] + ) } ) Then('scenario {string} {string} hook has status {string}', function( this: World, - pickleName, - hookKeyword, - status + pickleName: string, + hookKeyword: string, + status: string ) { const testStepResults = getTestStepResults(this.lastRun.envelopes, pickleName) const testStepResult = _.find(testStepResults, ['text', hookKeyword]) - expect(testStepResult.result.status).to.eql(Status[status.toUpperCase()]) + expect(testStepResult.result.status).to.eql( + Status[status.toUpperCase() as StringifiedStatus] + ) }) Then('scenario {string} step {string} failed with:', function( this: World, - pickleName, - stepText, - errorMessage + pickleName: string, + stepText: string, + errorMessage: string ) { const testStepResults = getTestStepResults(this.lastRun.envelopes, pickleName) const testStepResult = _.find(testStepResults, ['text', stepText]) @@ -113,10 +136,10 @@ Then('scenario {string} step {string} failed with:', function( Then('scenario {string} attempt {int} step {string} failed with:', function( this: World, - pickleName, - attempt, - stepText, - errorMessage + pickleName: string, + attempt: number, + stepText: string, + errorMessage: string ) { const testStepResults = getTestStepResults( this.lastRun.envelopes, @@ -130,9 +153,9 @@ Then('scenario {string} attempt {int} step {string} failed with:', function( Then('scenario {string} step {string} has the doc string:', function( this: World, - pickleName, - stepText, - docString + pickleName: string, + stepText: string, + docString: string ) { const pickleStep = getPickleStep(this.lastRun.envelopes, pickleName, stepText) expect(pickleStep.argument.docString.content).to.eql(docString) @@ -140,9 +163,9 @@ Then('scenario {string} step {string} has the doc string:', function( Then('scenario {string} step {string} has the data table:', function( this: World, - pickleName, - stepText, - dataTable + pickleName: string, + stepText: string, + dataTable: DataTable ) { const pickleStep = getPickleStep(this.lastRun.envelopes, pickleName, stepText) expect(new DataTable(pickleStep.argument.dataTable)).to.eql(dataTable) @@ -150,9 +173,9 @@ Then('scenario {string} step {string} has the data table:', function( Then('scenario {string} step {string} has the attachments:', function( this: World, - pickleName, - stepText, - table + pickleName: string, + stepText: string, + table: DataTable ) { const expectedAttachments = table.hashes().map(x => { return { @@ -176,9 +199,9 @@ Then('scenario {string} step {string} has the attachments:', function( Then('scenario {string} {string} hook has the attachments:', function( this: World, - pickleName, - hookKeyword, - table + pickleName: string, + hookKeyword: string, + table: DataTable ) { const expectedAttachments = table.hashes().map(x => { return { diff --git a/features/step_definitions/usage_json_steps.ts b/features/step_definitions/usage_json_steps.ts index 0213ffbde..b6ec874f6 100644 --- a/features/step_definitions/usage_json_steps.ts +++ b/features/step_definitions/usage_json_steps.ts @@ -1,12 +1,12 @@ import _ from 'lodash' -import { Then } from '../../' +import { Then, DataTable } from '../../' import { expect } from 'chai' import path from 'path' import { World } from '../support/world' -Then('it outputs the usage data:', function(this: World, table) { +Then('it outputs the usage data:', function(this: World, table: DataTable) { const usageData = JSON.parse(this.lastRun.output) - table.hashes().forEach(row => { + table.hashes().forEach((row: any) => { const rowUsage = _.find( usageData, datum => diff --git a/features/support/hooks.ts b/features/support/hooks.ts index fb6cdce41..8d8aba7ce 100644 --- a/features/support/hooks.ts +++ b/features/support/hooks.ts @@ -37,7 +37,7 @@ Before(function( const tmpDirNodeModulesPath = path.join(this.tmpDir, 'node_modules') const tmpDirCucumberPath = path.join(tmpDirNodeModulesPath, 'cucumber') - fsExtra.createSymlinkSync(projectPath, tmpDirCucumberPath) + fsExtra.ensureSymlinkSync(projectPath, tmpDirCucumberPath) this.localExecutablePath = path.join(projectPath, 'bin', 'cucumber-js') }) @@ -55,7 +55,7 @@ Before('@global-install', function(this: World) { 'node_modules', moduleName ) - fsExtra.createSymlinkSync( + fsExtra.ensureSymlinkSync( projectNodeModulePath, globalInstallNodeModulePath ) diff --git a/features/support/message_helpers.ts b/features/support/message_helpers.ts index c1b91099c..473dc6bfe 100644 --- a/features/support/message_helpers.ts +++ b/features/support/message_helpers.ts @@ -42,7 +42,10 @@ export function getPickleStep( return getPickleStepByStepText(pickle, gherkinDocument, stepText) } -export function getTestCaseResult(envelopes, pickleName): messages.ITestResult { +export function getTestCaseResult( + envelopes: messages.IEnvelope[], + pickleName: string +): messages.ITestResult { const pickle = getAcceptedPickle(envelopes, pickleName) const testCase = getTestCase(envelopes, pickle.id) const testCaseStartedId = getTestCaseStarted(envelopes, testCase.id).id diff --git a/package.json b/package.json index 857d07a00..b3b332563 100644 --- a/package.json +++ b/package.json @@ -185,10 +185,21 @@ }, "devDependencies": { "@types/bluebird": "^3.5.29", + "@types/chai": "^4.2.8", + "@types/dirty-chai": "^2.0.2", + "@types/fs-extra": "^8.0.1", "@types/glob": "^7.1.1", "@types/lodash": "^4.14.149", + "@types/lolex": "^5.1.0", + "@types/mocha": "^7.0.1", + "@types/mustache": "^4.0.0", "@types/mz": "^2.7.0", "@types/node": "^13.1.1", + "@types/progress": "^2.0.3", + "@types/resolve": "^1.14.0", + "@types/sinon-chai": "^3.2.3", + "@types/tmp": "^0.1.0", + "@types/verror": "^1.10.3", "@typescript-eslint/eslint-plugin": "^2.0.0", "@typescript-eslint/parser": "^2.0.0", "ansi-html": "^0.0.7", diff --git a/src/cli/configuration_builder.ts b/src/cli/configuration_builder.ts index 4599476e0..974ee6aa7 100644 --- a/src/cli/configuration_builder.ts +++ b/src/cli/configuration_builder.ts @@ -64,8 +64,8 @@ export default class ConfigurationBuilder { const listI18nKeywordsFor = this.options.i18nKeywords const listI18nLanguages = this.options.i18nLanguages const unexpandedFeaturePaths = await this.getUnexpandedFeaturePaths() - let featurePaths = [] - let supportCodePaths = [] + let featurePaths: string[] = [] + let supportCodePaths: string[] = [] if (listI18nKeywordsFor === '' && !listI18nLanguages) { featurePaths = await this.expandFeaturePaths(unexpandedFeaturePaths) let unexpandedSupportCodePaths = this.options.require @@ -156,7 +156,7 @@ export default class ConfigurationBuilder { } getFormats(): IConfigurationFormat[] { - const mapping = { '': 'progress' } + const mapping: { [key: string]: string } = { '': 'progress' } this.options.format.forEach(format => { const [type, outputTo] = OptionSplitter.split(format) mapping[outputTo] = type diff --git a/src/cli/configuration_builder_spec.ts b/src/cli/configuration_builder_spec.ts index 386b2019f..62db6ae53 100644 --- a/src/cli/configuration_builder_spec.ts +++ b/src/cli/configuration_builder_spec.ts @@ -3,13 +3,15 @@ import { expect } from 'chai' import ConfigurationBuilder from './configuration_builder' import fsExtra from 'fs-extra' import path from 'path' -import tmp from 'tmp' +import tmp, { DirOptions } from 'tmp' import { promisify } from 'util' import { SnippetInterface } from '../formatter/step_definition_snippet_builder/snippet_syntax' async function buildTestWorkingDirectory(): Promise { - const cwd = await promisify(tmp.dir)({ unsafeCleanup: true }) - await promisify(fsExtra.mkdirp)(path.join(cwd, 'features')) + const cwd = await promisify(tmp.dir)({ + unsafeCleanup: true, + }) + await fsExtra.mkdirp(path.join(cwd, 'features')) return cwd } diff --git a/src/cli/helpers.ts b/src/cli/helpers.ts index c0551a537..bcbf5ffbb 100644 --- a/src/cli/helpers.ts +++ b/src/cli/helpers.ts @@ -47,7 +47,7 @@ export async function parseGherkinMessageStream({ pickleFilter, }: IParseGherkinMessageStreamRequest): Promise { return new Promise((resolve, reject) => { - const result = [] + const result: string[] = [] gherkinMessageStream.on('data', envelope => { eventBroadcaster.emit('envelope', envelope) if (doesHaveValue(envelope.pickle)) { diff --git a/src/cli/helpers_spec.ts b/src/cli/helpers_spec.ts index 348865000..81aadf552 100644 --- a/src/cli/helpers_spec.ts +++ b/src/cli/helpers_spec.ts @@ -23,7 +23,7 @@ interface ITestParseGherkinMessageStreamResponse { async function testParseGherkinMessageStream( options: ITestParseGherkinMessageStreamRequest ): Promise { - const envelopes = [] + const envelopes: messages.IEnvelope[] = [] const eventBroadcaster = new EventEmitter() eventBroadcaster.on('envelope', e => envelopes.push(e)) const eventDataCollector = new EventDataCollector(eventBroadcaster) diff --git a/src/cli/i18n.ts b/src/cli/i18n.ts index 4226e08d5..b5c3e2272 100644 --- a/src/cli/i18n.ts +++ b/src/cli/i18n.ts @@ -14,7 +14,7 @@ const keywords = [ 'then', 'and', 'but', -] +] as const function getAsTable(header: string[], rows: string[][]): string { const table = new Table({ diff --git a/src/cli/index.ts b/src/cli/index.ts index ab1d4a2aa..e87f69364 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -22,7 +22,7 @@ import { doesNotHaveValue } from '../value_checker' import Gherkin from 'gherkin' import { ISupportCodeLibrary } from '../support_code_library_builder/types' import { IParsedArgvFormatOptions } from './argv_parser' - +import { WriteStream } from 'fs' const { incrementing, uuid } = IdGenerator export interface ICliRunResult { @@ -38,12 +38,26 @@ interface IInitializeFormattersRequest { supportCodeLibrary: ISupportCodeLibrary } +interface IGetSupportCodeLibraryRequest { + newId: IdGenerator.NewId + supportCodeRequiredModules: string[] + supportCodePaths: string[] +} + export default class Cli { private readonly argv: string[] private readonly cwd: string private readonly stdout: IFormatterStream - constructor({ argv, cwd, stdout }) { + constructor({ + argv, + cwd, + stdout, + }: { + argv: string[] + cwd: string + stdout: IFormatterStream + }) { this.argv = argv this.cwd = cwd this.stdout = stdout @@ -61,7 +75,7 @@ export default class Cli { formats, supportCodeLibrary, }: IInitializeFormattersRequest): Promise<() => Promise> { - const streamsToClose = [] + const streamsToClose: WriteStream[] = [] await bluebird.map(formats, async ({ type, outputTo }) => { let stream: IFormatterStream = this.stdout if (outputTo !== '') { @@ -101,7 +115,7 @@ export default class Cli { newId, supportCodeRequiredModules, supportCodePaths, - }): ISupportCodeLibrary { + }: IGetSupportCodeLibraryRequest): ISupportCodeLibrary { supportCodeRequiredModules.map(module => require(module)) supportCodeLibraryBuilder.reset(this.cwd, newId) supportCodePaths.forEach(codePath => require(codePath)) diff --git a/src/cli/install_validator.ts b/src/cli/install_validator.ts index 83854b390..d509ae264 100644 --- a/src/cli/install_validator.ts +++ b/src/cli/install_validator.ts @@ -9,7 +9,9 @@ export async function validateInstall(cwd: string): Promise { return // cucumber testing itself } const currentCucumberPath = require.resolve(projectPath) - let localCucumberPath = await promisify(resolve)('cucumber', { + let localCucumberPath: string = await promisify( + resolve + )('cucumber', { basedir: cwd, }) localCucumberPath = await fs.realpath(localCucumberPath) diff --git a/src/cli/profile_loader_spec.ts b/src/cli/profile_loader_spec.ts index 31385df19..3134b9719 100644 --- a/src/cli/profile_loader_spec.ts +++ b/src/cli/profile_loader_spec.ts @@ -3,7 +3,7 @@ import { expect } from 'chai' import fs from 'mz/fs' import path from 'path' import ProfileLoader from './profile_loader' -import tmp from 'tmp' +import tmp, { DirOptions } from 'tmp' import { promisify } from 'util' import { valueOrDefault, doesHaveValue } from '../value_checker' @@ -15,7 +15,9 @@ interface TestProfileLoaderOptions { async function testProfileLoader( opts: TestProfileLoaderOptions = {} ): Promise { - const cwd = await promisify(tmp.dir)({ unsafeCleanup: true }) + const cwd = await promisify(tmp.dir)({ + unsafeCleanup: true, + }) if (doesHaveValue(opts.definitionsFileContent)) { await fs.writeFile( path.join(cwd, 'cucumber.js'), diff --git a/src/formatter/get_color_fns.ts b/src/formatter/get_color_fns.ts index 9e69bafba..d3b666f1b 100644 --- a/src/formatter/get_color_fns.ts +++ b/src/formatter/get_color_fns.ts @@ -23,6 +23,7 @@ export default function getColorFns(enabled: boolean): IColorFns { [Status.PENDING]: colors.yellow.bind(colors), [Status.SKIPPED]: colors.cyan.bind(colors), [Status.UNDEFINED]: colors.yellow.bind(colors), + [Status.UNKNOWN]: colors.yellow.bind(colors), }[status] }, location: colors.gray.bind(colors), diff --git a/src/formatter/helpers/event_data_collector.ts b/src/formatter/helpers/event_data_collector.ts index cd72a7ca4..2a0bbb7ee 100644 --- a/src/formatter/helpers/event_data_collector.ts +++ b/src/formatter/helpers/event_data_collector.ts @@ -1,6 +1,7 @@ import _, { Dictionary } from 'lodash' import { messages } from 'cucumber-messages' import { doesHaveValue, doesNotHaveValue } from '../../value_checker' +import { EventEmitter } from 'events' interface ITestCaseAttemptData { attempt: number @@ -26,7 +27,7 @@ export default class EventDataCollector { private testCaseMap: Dictionary = {} private testCaseAttemptDataMap: Dictionary = {} - constructor(eventBroadcaster) { + constructor(eventBroadcaster: EventEmitter) { eventBroadcaster.on('envelope', this.parseEnvelope.bind(this)) } diff --git a/src/formatter/helpers/issue_helpers.ts b/src/formatter/helpers/issue_helpers.ts index 13ec9e173..65b8315aa 100644 --- a/src/formatter/helpers/issue_helpers.ts +++ b/src/formatter/helpers/issue_helpers.ts @@ -2,6 +2,10 @@ import indentString from 'indent-string' import Status from '../../status' import { formatTestCaseAttempt } from './test_case_attempt_formatter' import { messages } from 'cucumber-messages' +import { IColorFns } from '../get_color_fns' +import StepDefinitionSnippetBuilder from '../step_definition_snippet_builder' +import { ISupportCodeLibrary } from '../../support_code_library_builder/types' +import { ITestCaseAttempt } from './event_data_collector' export function isFailure(result: messages.ITestResult): boolean { return ( @@ -22,6 +26,15 @@ export function isIssue(result: messages.ITestResult): boolean { return isFailure(result) || isWarning(result) } +export interface IFormatIssueRequest { + colorFns: IColorFns + cwd: string + number: number + snippetBuilder: StepDefinitionSnippetBuilder + testCaseAttempt: ITestCaseAttempt + supportCodeLibrary: ISupportCodeLibrary +} + export function formatIssue({ colorFns, cwd, @@ -29,7 +42,7 @@ export function formatIssue({ snippetBuilder, testCaseAttempt, supportCodeLibrary, -}): string { +}: IFormatIssueRequest): string { const prefix = `${number}) ` const formattedTestCaseAttempt = formatTestCaseAttempt({ colorFns, diff --git a/src/formatter/helpers/keyword_type.ts b/src/formatter/helpers/keyword_type.ts index 61fc9faa4..668315e92 100644 --- a/src/formatter/helpers/keyword_type.ts +++ b/src/formatter/helpers/keyword_type.ts @@ -1,6 +1,7 @@ import _ from 'lodash' import Gherkin from 'gherkin' import { doesHaveValue } from '../../value_checker' +import Dialect from 'gherkin/dist/src/Dialect' export enum KeywordType { Precondition = 'precondition', @@ -19,10 +20,9 @@ export function getStepKeywordType({ language, previousKeywordType, }: IGetStepKeywordTypeOptions): KeywordType { - const dialect = Gherkin.dialects()[language] - const type = _.find(['given', 'when', 'then', 'and', 'but'], key => - _.includes(dialect[key], keyword) - ) + const dialect: Dialect = Gherkin.dialects()[language] + const stepKeywords = ['given', 'when', 'then', 'and', 'but'] as const + const type = _.find(stepKeywords, key => _.includes(dialect[key], keyword)) switch (type) { case 'when': return KeywordType.Event diff --git a/src/formatter/helpers/summary_helpers.ts b/src/formatter/helpers/summary_helpers.ts index 4247cc320..450db6c1b 100644 --- a/src/formatter/helpers/summary_helpers.ts +++ b/src/formatter/helpers/summary_helpers.ts @@ -77,7 +77,7 @@ function getCountSummary({ .value() let text = `${total} ${type}${total === 1 ? '' : 's'}` if (total > 0) { - const details = [] + const details: string[] = [] STATUS_REPORT_ORDER.forEach(status => { if (counts[status] > 0) { details.push( diff --git a/src/formatter/helpers/summary_helpers_spec.ts b/src/formatter/helpers/summary_helpers_spec.ts index bbcb93263..90b5da7e4 100644 --- a/src/formatter/helpers/summary_helpers_spec.ts +++ b/src/formatter/helpers/summary_helpers_spec.ts @@ -4,7 +4,7 @@ import getColorFns from '../get_color_fns' import { formatSummary } from './summary_helpers' import { getTestCaseAttempts } from '../../../test/formatter_helpers' import { getBaseSupportCodeLibrary } from '../../../test/fixtures/steps' -import lolex from 'lolex' +import lolex, { InstalledClock } from 'lolex' import timeMethods from '../../time' import { buildSupportCodeLibrary } from '../../../test/runtime_helpers' import { IRuntimeOptions } from '../../runtime' @@ -43,7 +43,7 @@ async function testFormatSummary({ } describe('SummaryHelpers', () => { - let clock + let clock: InstalledClock beforeEach(() => { clock = lolex.install({ target: timeMethods }) diff --git a/src/formatter/helpers/test_case_attempt_formatter.ts b/src/formatter/helpers/test_case_attempt_formatter.ts index b080bde9d..b6bd3aef1 100644 --- a/src/formatter/helpers/test_case_attempt_formatter.ts +++ b/src/formatter/helpers/test_case_attempt_formatter.ts @@ -9,8 +9,11 @@ import { import { formatStepArgument } from './step_argument_formatter' import { IColorFns } from '../get_color_fns' import { valueOrDefault, doesHaveValue } from '../../value_checker' +import { ITestCaseAttempt } from './event_data_collector' +import StepDefinitionSnippetBuilder from '../step_definition_snippet_builder' +import { ISupportCodeLibrary } from '../../support_code_library_builder/types' -const CHARACTERS = { +const CHARACTERS: { [status: number]: string } = { [Status.AMBIGUOUS]: figures.cross, [Status.FAILED]: figures.cross, [Status.PASSED]: figures.tick, @@ -66,13 +69,21 @@ function formatStep({ colorFns, testStep }: IFormatStepRequest): string { return text } +export interface IFormatTestCaseAttemptRequest { + colorFns: IColorFns + cwd: string + testCaseAttempt: ITestCaseAttempt + snippetBuilder: StepDefinitionSnippetBuilder + supportCodeLibrary: ISupportCodeLibrary +} + export function formatTestCaseAttempt({ colorFns, cwd, snippetBuilder, supportCodeLibrary, testCaseAttempt, -}): string { +}: IFormatTestCaseAttemptRequest): string { const parsed = parseTestCaseAttempt({ cwd, snippetBuilder, diff --git a/src/formatter/helpers/usage_helpers/index.ts b/src/formatter/helpers/usage_helpers/index.ts index 0fbf5d94d..b635040e2 100644 --- a/src/formatter/helpers/usage_helpers/index.ts +++ b/src/formatter/helpers/usage_helpers/index.ts @@ -34,7 +34,7 @@ export interface IGetUsageRequest { function buildEmptyMapping( stepDefinitions: StepDefinition[] ): Dictionary { - const mapping = {} + const mapping: Dictionary = {} stepDefinitions.forEach(stepDefinition => { mapping[stepDefinition.id] = { code: stepDefinition.unwrappedCode.toString(), diff --git a/src/formatter/helpers/usage_helpers/index_spec.ts b/src/formatter/helpers/usage_helpers/index_spec.ts index 798d52976..5b72a7444 100644 --- a/src/formatter/helpers/usage_helpers/index_spec.ts +++ b/src/formatter/helpers/usage_helpers/index_spec.ts @@ -43,8 +43,8 @@ describe('Usage Helpers', () => { ({ Given, setDefinitionFunctionWrapper }) => { Given('a step', code) setDefinitionFunctionWrapper( - fn => - function(fn) { + (fn: Function) => + function(fn: Function) { if (fn.length === 1) { return fn } diff --git a/src/formatter/index.ts b/src/formatter/index.ts index 7282f0fa3..696e96d29 100644 --- a/src/formatter/index.ts +++ b/src/formatter/index.ts @@ -1,14 +1,14 @@ import { IColorFns } from './get_color_fns' import { EventDataCollector } from './helpers' import StepDefinitionSnippetBuilder from './step_definition_snippet_builder' -import { Writable as WritableStream } from 'stream' +import { PassThrough, Writable as WritableStream } from 'stream' import { ISupportCodeLibrary } from '../support_code_library_builder/types' import { WriteStream as FsWriteStream } from 'fs' import { WriteStream as TtyWriteStream } from 'tty' import { EventEmitter } from 'events' import { IParsedArgvFormatOptions } from '../cli/argv_parser' -export type IFormatterStream = FsWriteStream | TtyWriteStream +export type IFormatterStream = FsWriteStream | TtyWriteStream | PassThrough export type IFormatterLogFn = (buffer: string | Uint8Array) => void export interface IFormatterOptions { diff --git a/src/formatter/json_formatter.ts b/src/formatter/json_formatter.ts index 2f411f78c..79a50da7c 100644 --- a/src/formatter/json_formatter.ts +++ b/src/formatter/json_formatter.ts @@ -1,5 +1,5 @@ import _, { Dictionary } from 'lodash' -import Formatter from './' +import Formatter, { IFormatterOptions } from './' import Status from '../status' import { formatLocation, GherkinDocumentParser, PickleParser } from './helpers' import { durationToNanoseconds } from '../time' @@ -9,6 +9,11 @@ import { getGherkinScenarioLocationMap } from './helpers/gherkin_document_parser import { ITestCaseAttempt } from './helpers/event_data_collector' import { doesHaveValue, doesNotHaveValue } from '../value_checker' import { parseStepArgument } from '../step_arguments' +import ITag = messages.GherkinDocument.Feature.ITag +import IFeature = messages.GherkinDocument.IFeature +import IPickle = messages.IPickle +import IScenario = messages.GherkinDocument.Feature.IScenario +import IEnvelope = messages.IEnvelope const { getGherkinStepMap, getGherkinScenarioMap } = GherkinDocumentParser @@ -84,16 +89,16 @@ interface UriToTestCaseAttemptsMap { } export default class JsonFormatter extends Formatter { - constructor(options) { + constructor(options: IFormatterOptions) { super(options) - options.eventBroadcaster.on('envelope', envelope => { + options.eventBroadcaster.on('envelope', (envelope: IEnvelope) => { if (doesHaveValue(envelope.testRunFinished)) { this.onTestRunFinished() } }) } - convertNameToId(obj): string { + convertNameToId(obj: IFeature | IPickle): string { return obj.name.replace(/ /g, '-').toLowerCase() } @@ -266,18 +271,28 @@ export default class JsonFormatter extends Formatter { return data } - getFeatureTags(feature): IJsonTag[] { + getFeatureTags(feature: IFeature): IJsonTag[] { return _.map(feature.tags, tagData => ({ name: tagData.name, line: tagData.location.line, })) } - getScenarioTags({ feature, pickle, gherkinScenarioMap }): IJsonTag[] { + getScenarioTags({ + feature, + pickle, + gherkinScenarioMap, + }: { + feature: IFeature + pickle: IPickle + gherkinScenarioMap: { [id: string]: IScenario } + }): IJsonTag[] { return _.map(pickle.tags, tagData => { - const featureSource = feature.tags.find(t => t.id === tagData.astNodeId) + const featureSource = feature.tags.find( + (t: ITag) => t.id === tagData.astNodeId + ) const scenarioSource = gherkinScenarioMap[pickle.astNodeIds[0]].tags.find( - t => t.id === tagData.astNodeId + (t: ITag) => t.id === tagData.astNodeId ) const line = doesHaveValue(featureSource) ? featureSource.location.line diff --git a/src/formatter/json_formatter_spec.ts b/src/formatter/json_formatter_spec.ts index db2154bd2..b3c83885b 100644 --- a/src/formatter/json_formatter_spec.ts +++ b/src/formatter/json_formatter_spec.ts @@ -5,11 +5,11 @@ import { getJsonFormatterSupportCodeLibrary, getJsonFormatterSupportCodeLibraryWithHooks, } from '../../test/fixtures/json_formatter_steps' -import lolex from 'lolex' +import lolex, { InstalledClock } from 'lolex' import timeMethods from '../time' describe('JsonFormatter', () => { - let clock + let clock: InstalledClock beforeEach(() => { clock = lolex.install({ target: timeMethods }) @@ -77,7 +77,7 @@ describe('JsonFormatter', () => { arguments: [], line: 8, match: { - location: 'json_formatter_steps.ts:8', + location: 'json_formatter_steps.ts:11', }, keyword: 'Given ', name: 'a passing step', diff --git a/src/formatter/message_formatter.ts b/src/formatter/message_formatter.ts index 0ffe7ba12..688ea9ea2 100644 --- a/src/formatter/message_formatter.ts +++ b/src/formatter/message_formatter.ts @@ -1,10 +1,11 @@ -import Formatter from '.' +import Formatter, { IFormatterOptions } from '.' import { messages } from 'cucumber-messages' +import IEnvelope = messages.IEnvelope export default class MessageFormatter extends Formatter { - constructor(options) { + constructor(options: IFormatterOptions) { super(options) - options.eventBroadcaster.on('envelope', envelope => + options.eventBroadcaster.on('envelope', (envelope: IEnvelope) => this.log(messages.Envelope.encodeDelimited(envelope).finish()) ) } diff --git a/src/formatter/progress_bar_formatter.ts b/src/formatter/progress_bar_formatter.ts index d0dd67d9f..da9e1fe1b 100644 --- a/src/formatter/progress_bar_formatter.ts +++ b/src/formatter/progress_bar_formatter.ts @@ -1,5 +1,5 @@ import { formatIssue, formatSummary, isIssue } from './helpers' -import Formatter from './' +import Formatter, { IFormatterOptions } from './' import ProgressBar from 'progress' import { WriteStream as TtyWriteStream } from 'tty' import { messages } from 'cucumber-messages' @@ -11,7 +11,7 @@ export default class ProgressBarFormatter extends Formatter { private issueCount: number public progressBar: ProgressBar - constructor(options) { + constructor(options: IFormatterOptions) { super(options) options.eventBroadcaster.on('envelope', this.parseEnvelope.bind(this)) this.numberOfSteps = 0 diff --git a/src/formatter/progress_bar_formatter_spec.ts b/src/formatter/progress_bar_formatter_spec.ts index 413bd407e..271903612 100644 --- a/src/formatter/progress_bar_formatter_spec.ts +++ b/src/formatter/progress_bar_formatter_spec.ts @@ -1,6 +1,6 @@ import { beforeEach, afterEach, describe, it } from 'mocha' import { expect } from 'chai' -import sinon from 'sinon' +import sinon, { SinonStubbedInstance } from 'sinon' import { EventEmitter } from 'events' import { EventDataCollector } from './helpers' import { @@ -10,7 +10,7 @@ import { import { buildSupportCodeLibrary } from '../../test/runtime_helpers' import FormatterBuilder from './builder' import { getBaseSupportCodeLibrary } from '../../test/fixtures/steps' -import lolex from 'lolex' +import lolex, { InstalledClock } from 'lolex' import timeMethods from '../time' import { IRuntimeOptions } from '../runtime' import { messages } from 'cucumber-messages' @@ -18,6 +18,7 @@ import { ISupportCodeLibrary } from '../support_code_library_builder/types' import ProgressBarFormatter from './progress_bar_formatter' import { doesHaveValue, doesNotHaveValue } from '../value_checker' import { PassThrough } from 'stream' +import ProgressBar from 'progress' interface ITestProgressBarFormatterOptions { runtimeOptions?: Partial @@ -69,6 +70,12 @@ async function testProgressBarFormatter({ progressBarFormatter.progressBar = { interrupt: sinon.stub(), tick: sinon.stub(), + render: sinon.stub(), + update: sinon.stub(), + terminate: sinon.stub(), + complete: false, + curr: null, + total: null, } mocked = true } @@ -319,25 +326,20 @@ describe('ProgressBarFormatter', () => { sources, supportCodeLibrary, }) + const progressBar = progressBarFormatter.progressBar as sinon.SinonStubbedInstance< + ProgressBar + > // Assert - expect(progressBarFormatter.progressBar.interrupt).to.have.callCount(1) - expect(progressBarFormatter.progressBar.tick).to.have.callCount(7) - expect(progressBarFormatter.progressBar.tick.args).to.eql([ - [], - [], - [], - [-3], - [], - [], - [], - ]) + expect(progressBar.interrupt).to.have.callCount(1) + expect(progressBar.tick).to.have.callCount(7) + expect(progressBar.tick.args).to.deep.eq([[], [], [], [-3], [], [], []]) }) }) }) describe('testRunFinished', () => { - let clock + let clock: InstalledClock beforeEach(() => { clock = lolex.install({ target: timeMethods }) diff --git a/src/formatter/progress_formatter.ts b/src/formatter/progress_formatter.ts index 37ab56684..074f81dc3 100644 --- a/src/formatter/progress_formatter.ts +++ b/src/formatter/progress_formatter.ts @@ -1,8 +1,12 @@ import SummaryFormatter from './summary_formatter' import Status from '../status' import { doesHaveValue } from '../value_checker' +import { IFormatterOptions } from './index' +import { messages } from 'cucumber-messages' +import IEnvelope = messages.IEnvelope +import ITestStepFinished = messages.ITestStepFinished -const STATUS_CHARACTER_MAPPING = { +const STATUS_CHARACTER_MAPPING: { [key: number]: string } = { [Status.AMBIGUOUS]: 'A', [Status.FAILED]: 'F', [Status.PASSED]: '.', @@ -12,8 +16,8 @@ const STATUS_CHARACTER_MAPPING = { } export default class ProgressFormatter extends SummaryFormatter { - constructor(options) { - options.eventBroadcaster.on('envelope', envelope => { + constructor(options: IFormatterOptions) { + options.eventBroadcaster.on('envelope', (envelope: IEnvelope) => { if (doesHaveValue(envelope.testRunFinished)) { this.log('\n\n') } else if (doesHaveValue(envelope.testStepFinished)) { @@ -23,7 +27,7 @@ export default class ProgressFormatter extends SummaryFormatter { super(options) } - logProgress({ testResult: { status } }): void { + logProgress({ testResult: { status } }: ITestStepFinished): void { const character = this.colorFns.forStatus(status)( STATUS_CHARACTER_MAPPING[status] ) diff --git a/src/formatter/progress_formatter_spec.ts b/src/formatter/progress_formatter_spec.ts index fce46805e..c6ad6882a 100644 --- a/src/formatter/progress_formatter_spec.ts +++ b/src/formatter/progress_formatter_spec.ts @@ -3,11 +3,11 @@ import { expect } from 'chai' import { getBaseSupportCodeLibrary } from '../../test/fixtures/steps' import { testFormatter } from '../../test/formatter_helpers' import figures from 'figures' -import lolex from 'lolex' +import lolex, { InstalledClock } from 'lolex' import timeMethods from '../time' describe('ProgressFormatter', () => { - let clock + let clock: InstalledClock beforeEach(() => { clock = lolex.install({ target: timeMethods }) diff --git a/src/formatter/snippets_formatter.ts b/src/formatter/snippets_formatter.ts index 27d9d4187..0da4f0146 100644 --- a/src/formatter/snippets_formatter.ts +++ b/src/formatter/snippets_formatter.ts @@ -1,12 +1,14 @@ -import Formatter from './' +import Formatter, { IFormatterOptions } from './' import Status from '../status' import { parseTestCaseAttempt } from './helpers' import { doesHaveValue } from '../value_checker' +import { messages } from 'cucumber-messages' +import IEnvelope = messages.IEnvelope export default class SnippetsFormatter extends Formatter { - constructor(options) { + constructor(options: IFormatterOptions) { super(options) - options.eventBroadcaster.on('envelope', envelope => { + options.eventBroadcaster.on('envelope', (envelope: IEnvelope) => { if (doesHaveValue(envelope.testRunFinished)) { this.logSnippets() } @@ -14,7 +16,7 @@ export default class SnippetsFormatter extends Formatter { } logSnippets(): void { - const snippets = [] + const snippets: string[] = [] this.eventDataCollector.getTestCaseAttempts().map(testCaseAttempt => { const parsed = parseTestCaseAttempt({ cwd: this.cwd, diff --git a/src/formatter/summary_formatter.ts b/src/formatter/summary_formatter.ts index 775d9579a..b17168718 100644 --- a/src/formatter/summary_formatter.ts +++ b/src/formatter/summary_formatter.ts @@ -1,6 +1,6 @@ import _ from 'lodash' import { formatIssue, formatSummary, isFailure, isWarning } from './helpers' -import Formatter from './' +import Formatter, { IFormatterOptions } from './' import { doesHaveValue } from '../value_checker' import { messages } from 'cucumber-messages' import { ITestCaseAttempt } from './helpers/event_data_collector' @@ -11,7 +11,7 @@ interface ILogIssuesRequest { } export default class SummaryFormatter extends Formatter { - constructor(options) { + constructor(options: IFormatterOptions) { super(options) options.eventBroadcaster.on('envelope', (envelope: messages.IEnvelope) => { if (doesHaveValue(envelope.testRunFinished)) { diff --git a/src/formatter/summary_formatter_spec.ts b/src/formatter/summary_formatter_spec.ts index ea874ce70..556937a68 100644 --- a/src/formatter/summary_formatter_spec.ts +++ b/src/formatter/summary_formatter_spec.ts @@ -1,13 +1,13 @@ import { beforeEach, afterEach, describe, it } from 'mocha' import { expect } from 'chai' import figures from 'figures' -import lolex from 'lolex' +import lolex, { InstalledClock } from 'lolex' import timeMethods from '../time' import { testFormatter } from '../../test/formatter_helpers' import { getBaseSupportCodeLibrary } from '../../test/fixtures/steps' describe('SummaryFormatter', () => { - let clock + let clock: InstalledClock beforeEach(() => { clock = lolex.install({ target: timeMethods }) diff --git a/src/formatter/usage_formatter.ts b/src/formatter/usage_formatter.ts index 51d4b3262..706a13330 100644 --- a/src/formatter/usage_formatter.ts +++ b/src/formatter/usage_formatter.ts @@ -1,14 +1,16 @@ import _ from 'lodash' import { formatLocation, getUsage } from './helpers' -import Formatter from './' +import Formatter, { IFormatterOptions } from './' import Table from 'cli-table3' import { durationToMilliseconds } from '../time' import { doesHaveValue } from '../value_checker' +import { messages } from 'cucumber-messages' +import IEnvelope = messages.IEnvelope export default class UsageFormatter extends Formatter { - constructor(options) { + constructor(options: IFormatterOptions) { super(options) - options.eventBroadcaster.on('envelope', envelope => { + options.eventBroadcaster.on('envelope', (envelope: IEnvelope) => { if (doesHaveValue(envelope.testRunFinished)) { this.logUsage() } diff --git a/src/formatter/usage_formatter_spec.ts b/src/formatter/usage_formatter_spec.ts index 9316bbe96..3e25346bf 100644 --- a/src/formatter/usage_formatter_spec.ts +++ b/src/formatter/usage_formatter_spec.ts @@ -1,12 +1,12 @@ import { afterEach, beforeEach, describe, it } from 'mocha' import { expect } from 'chai' -import lolex from 'lolex' +import lolex, { InstalledClock } from 'lolex' import timeMethods from '../time' import { getUsageSupportCodeLibrary } from '../../test/fixtures/usage_steps' import { testFormatter } from '../../test/formatter_helpers' describe('UsageFormatter', () => { - let clock + let clock: InstalledClock beforeEach(() => { clock = lolex.install({ target: timeMethods }) @@ -45,11 +45,11 @@ describe('UsageFormatter', () => { ┌────────────────┬──────────┬───────────────────┐ │ Pattern / Text │ Duration │ Location │ ├────────────────┼──────────┼───────────────────┤ -│ abc │ UNUSED │ usage_steps.ts:8 │ +│ abc │ UNUSED │ usage_steps.ts:11 │ ├────────────────┼──────────┼───────────────────┤ -│ /def?/ │ UNUSED │ usage_steps.ts:13 │ +│ /def?/ │ UNUSED │ usage_steps.ts:16 │ ├────────────────┼──────────┼───────────────────┤ -│ ghi │ UNUSED │ usage_steps.ts:22 │ +│ ghi │ UNUSED │ usage_steps.ts:25 │ └────────────────┴──────────┴───────────────────┘ `) }) @@ -81,13 +81,13 @@ describe('UsageFormatter', () => { ┌────────────────┬──────────┬───────────────────┐ │ Pattern / Text │ Duration │ Location │ ├────────────────┼──────────┼───────────────────┤ -│ abc │ UNUSED │ usage_steps.ts:8 │ +│ abc │ UNUSED │ usage_steps.ts:11 │ ├────────────────┼──────────┼───────────────────┤ -│ /def?/ │ - │ usage_steps.ts:13 │ +│ /def?/ │ - │ usage_steps.ts:16 │ │ de │ - │ a.feature:4 │ │ def │ - │ a.feature:3 │ ├────────────────┼──────────┼───────────────────┤ -│ ghi │ UNUSED │ usage_steps.ts:22 │ +│ ghi │ UNUSED │ usage_steps.ts:25 │ └────────────────┴──────────┴───────────────────┘ `) }) @@ -116,13 +116,13 @@ describe('UsageFormatter', () => { ┌────────────────┬──────────┬───────────────────┐ │ Pattern / Text │ Duration │ Location │ ├────────────────┼──────────┼───────────────────┤ -│ /def?/ │ 1.50ms │ usage_steps.ts:13 │ +│ /def?/ │ 1.50ms │ usage_steps.ts:16 │ │ def │ 2ms │ a.feature:3 │ │ de │ 1ms │ a.feature:4 │ ├────────────────┼──────────┼───────────────────┤ -│ abc │ UNUSED │ usage_steps.ts:8 │ +│ abc │ UNUSED │ usage_steps.ts:11 │ ├────────────────┼──────────┼───────────────────┤ -│ ghi │ UNUSED │ usage_steps.ts:22 │ +│ ghi │ UNUSED │ usage_steps.ts:25 │ └────────────────┴──────────┴───────────────────┘ `) }) diff --git a/src/formatter/usage_json_formatter.ts b/src/formatter/usage_json_formatter.ts index 986d09977..3f9ddd857 100644 --- a/src/formatter/usage_json_formatter.ts +++ b/src/formatter/usage_json_formatter.ts @@ -1,11 +1,13 @@ import { getUsage } from './helpers' -import Formatter from './' +import Formatter, { IFormatterOptions } from './' import { doesHaveValue } from '../value_checker' +import { messages } from 'cucumber-messages' +import IEnvelope = messages.IEnvelope export default class UsageJsonFormatter extends Formatter { - constructor(options) { + constructor(options: IFormatterOptions) { super(options) - options.eventBroadcaster.on('envelope', envelope => { + options.eventBroadcaster.on('envelope', (envelope: IEnvelope) => { if (doesHaveValue(envelope.testRunFinished)) { this.logUsage() } diff --git a/src/formatter/usage_json_formatter_spec.ts b/src/formatter/usage_json_formatter_spec.ts index d671fa107..98e2afb75 100644 --- a/src/formatter/usage_json_formatter_spec.ts +++ b/src/formatter/usage_json_formatter_spec.ts @@ -1,12 +1,12 @@ import { afterEach, beforeEach, describe, it } from 'mocha' import { expect } from 'chai' -import lolex from 'lolex' +import lolex, { InstalledClock } from 'lolex' import timeMethods from '../time' import { getUsageSupportCodeLibrary } from '../../test/fixtures/usage_steps' import { testFormatter } from '../../test/formatter_helpers' describe('UsageJsonFormatter', () => { - let clock + let clock: InstalledClock beforeEach(() => { clock = lolex.install({ target: timeMethods }) @@ -38,7 +38,7 @@ describe('UsageJsonFormatter', () => { expect(parsedOutput).to.eql([ { code: parsedOutput[0].code, - line: 13, + line: 16, matches: [ { duration: { @@ -60,7 +60,7 @@ describe('UsageJsonFormatter', () => { }, { code: parsedOutput[1].code, - line: 8, + line: 11, matches: [ { duration: { @@ -82,7 +82,7 @@ describe('UsageJsonFormatter', () => { }, { code: parsedOutput[2].code, - line: 22, + line: 25, matches: [], pattern: 'ghi', patternType: 'CucumberExpression', diff --git a/src/index.ts b/src/index.ts index 0dd0c5ce4..e8dc3c138 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,7 @@ export { default as PickleFilter } from './pickle_filter' export { default as Runtime } from './runtime' export { default as supportCodeLibraryBuilder } from './support_code_library_builder' export { default as Status } from './status' +export { default as DataTable } from './models/data_table' // Formatters export { default as Formatter } from './formatter' diff --git a/src/models/definition.ts b/src/models/definition.ts index 67ab9eb16..0f4463714 100644 --- a/src/models/definition.ts +++ b/src/models/definition.ts @@ -1,5 +1,6 @@ import { messages } from 'cucumber-messages' import { ITestCaseHookParameter } from '../support_code_library_builder/types' +import { Expression } from 'cucumber-expressions' export interface IGetInvocationDataRequest { hookParameter: ITestCaseHookParameter @@ -14,8 +15,27 @@ export interface IGetInvocationDataResponse { } export interface IDefinitionOptions { - timeout: number - wrapperOptions: any + timeout?: number + wrapperOptions?: any +} + +export interface IHookDefinitionOptions extends IDefinitionOptions { + tags?: string +} + +export interface IDefinitionParameters { + code: Function + id: string + line: number + options: T + unwrappedCode?: Function + uri: string +} + +export interface IStepDefinitionParameters + extends IDefinitionParameters { + pattern: string | RegExp + expression: Expression } export interface IDefinition { @@ -39,7 +59,14 @@ export default abstract class Definition { public readonly unwrappedCode: Function public readonly uri: string - constructor({ code, id, line, options, unwrappedCode, uri }) { + constructor({ + code, + id, + line, + options, + unwrappedCode, + uri, + }: IDefinitionParameters) { this.code = code this.id = id this.line = line diff --git a/src/models/step_definition.ts b/src/models/step_definition.ts index 9160c1cd8..89dbc39e4 100644 --- a/src/models/step_definition.ts +++ b/src/models/step_definition.ts @@ -3,6 +3,7 @@ import Definition, { IDefinition, IGetInvocationDataRequest, IGetInvocationDataResponse, + IStepDefinitionParameters, } from './definition' import { parseStepArgument } from '../step_arguments' import { Expression } from 'cucumber-expressions' @@ -10,10 +11,10 @@ import bluebird from 'bluebird' import { doesHaveValue } from '../value_checker' export default class StepDefinition extends Definition implements IDefinition { - public readonly pattern: string + public readonly pattern: string | RegExp public readonly expression: Expression - constructor(data) { + constructor(data: IStepDefinitionParameters) { super(data) this.pattern = data.pattern this.expression = data.expression @@ -41,7 +42,7 @@ export default class StepDefinition extends Definition implements IDefinition { } } - matchesStepName(stepName): boolean { + matchesStepName(stepName: string): boolean { return doesHaveValue(this.expression.match(stepName)) } } diff --git a/src/models/test_case_hook_definition.ts b/src/models/test_case_hook_definition.ts index 8f9a22c95..5dd50a564 100644 --- a/src/models/test_case_hook_definition.ts +++ b/src/models/test_case_hook_definition.ts @@ -3,6 +3,8 @@ import Definition, { IDefinition, IGetInvocationDataResponse, IGetInvocationDataRequest, + IDefinitionParameters, + IHookDefinitionOptions, } from './definition' import { messages } from 'cucumber-messages' @@ -10,7 +12,7 @@ export default class TestCaseHookDefinition extends Definition implements IDefinition { private readonly pickleTagFilter: PickleTagFilter - constructor(data) { + constructor(data: IDefinitionParameters) { super(data) this.pickleTagFilter = new PickleTagFilter(data.options.tags) } diff --git a/src/models/test_case_hook_definition_spec.ts b/src/models/test_case_hook_definition_spec.ts index b59e618b0..bb502c551 100644 --- a/src/models/test_case_hook_definition_spec.ts +++ b/src/models/test_case_hook_definition_spec.ts @@ -10,6 +10,10 @@ describe('TestCaseHookDefinition', () => { // Arrange const pickle = await getPickleWithTags([]) const testCaseHookDefinition = new TestCaseHookDefinition({ + code: undefined, + id: '', + line: 0, + uri: '', options: {}, }) @@ -26,6 +30,10 @@ describe('TestCaseHookDefinition', () => { // Arrange const pickle = await getPickleWithTags(['@tagA']) const testCaseHookDefinition = new TestCaseHookDefinition({ + code: undefined, + id: '', + line: 0, + uri: '', options: { tags: '@tagA' }, }) @@ -42,6 +50,10 @@ describe('TestCaseHookDefinition', () => { // Arrange const pickle = await getPickleWithTags([]) const testCaseHookDefinition = new TestCaseHookDefinition({ + code: undefined, + id: '', + line: 0, + uri: '', options: { tags: '@tagA' }, }) diff --git a/src/pickle_filter.ts b/src/pickle_filter.ts index 308a52c6f..18c82902d 100644 --- a/src/pickle_filter.ts +++ b/src/pickle_filter.ts @@ -2,8 +2,10 @@ import _, { Dictionary } from 'lodash' import path from 'path' import parse from 'cucumber-tag-expressions' import { getGherkinScenarioLocationMap } from './formatter/helpers/gherkin_document_parser' -import { messages } from 'cucumber-messages' import { doesHaveValue, doesNotHaveValue } from './value_checker' +import { messages } from 'cucumber-messages' +import IGherkinDocument = messages.IGherkinDocument +import IPickle = messages.IPickle const FEATURE_LINENUM_REGEXP = /^(.*?)((?::[\d]+)+)?$/ @@ -35,7 +37,13 @@ export default class PickleFilter { this.tagFilter = new PickleTagFilter(tagExpression) } - matches({ gherkinDocument, pickle }): boolean { + matches({ + gherkinDocument, + pickle, + }: { + gherkinDocument: IGherkinDocument + pickle: IPickle + }): boolean { return ( this.lineFilter.matchesAnyLine({ gherkinDocument, pickle }) && this.nameFilter.matchesAnyName(pickle) && @@ -54,7 +62,13 @@ export class PickleLineFilter { }) } - getFeatureUriToLinesMapping({ cwd, featurePaths }): Dictionary { + getFeatureUriToLinesMapping({ + cwd, + featurePaths, + }: { + cwd: string + featurePaths: string[] + }): Dictionary { const mapping: Dictionary = {} featurePaths.forEach(featurePath => { const match = FEATURE_LINENUM_REGEXP.exec(featurePath) diff --git a/src/pickle_filter_spec.ts b/src/pickle_filter_spec.ts index d0a201e21..8b1091ec9 100644 --- a/src/pickle_filter_spec.ts +++ b/src/pickle_filter_spec.ts @@ -6,7 +6,7 @@ import { parse } from '../test/gherkin_helpers' describe('PickleFilter', () => { const cwd = '/project' - let pickleFilter + let pickleFilter: PickleFilter describe('matches', () => { describe('no filters', () => { diff --git a/src/runtime/attachment_manager/index.ts b/src/runtime/attachment_manager/index.ts index 28aaee561..b6138cd42 100644 --- a/src/runtime/attachment_manager/index.ts +++ b/src/runtime/attachment_manager/index.ts @@ -8,6 +8,7 @@ export interface IAttachment { media: messages.Media } +export type IAttachFunction = (attachment: IAttachment) => void export type ICreateAttachment = ( data: Buffer | stream.Readable | string, mediaType?: string, @@ -15,9 +16,9 @@ export type ICreateAttachment = ( ) => void | Promise export default class AttachmentManager { - private readonly onAttachment: (attachment: IAttachment) => void + private readonly onAttachment: IAttachFunction - constructor(onAttachment: (attachment: IAttachment) => void) { + constructor(onAttachment: IAttachFunction) { this.onAttachment = onAttachment } @@ -70,7 +71,7 @@ export default class AttachmentManager { callback: () => void ): void | Promise { const promise = new Promise((resolve, reject) => { - const buffers = [] + const buffers: Uint8Array[] = [] data.on('data', chunk => { buffers.push(chunk) }) diff --git a/src/runtime/attachment_manager/index_spec.ts b/src/runtime/attachment_manager/index_spec.ts index 089126085..0b25e00db 100644 --- a/src/runtime/attachment_manager/index_spec.ts +++ b/src/runtime/attachment_manager/index_spec.ts @@ -1,6 +1,6 @@ import { describe, it } from 'mocha' import { expect } from 'chai' -import AttachmentManager from './' +import AttachmentManager, { IAttachment } from './' import stream, { Readable } from 'stream' import { messages } from 'cucumber-messages' @@ -10,7 +10,7 @@ describe('AttachmentManager', () => { describe('with mime type', () => { it('adds the data and media', function() { // Arrange - const attachments = [] + const attachments: IAttachment[] = [] const attachmentManager = new AttachmentManager(x => attachments.push(x) ) @@ -43,7 +43,7 @@ describe('AttachmentManager', () => { describe('without media type', () => { it('throws', function() { // Arrange - const attachments = [] + const attachments: IAttachment[] = [] const attachmentManager = new AttachmentManager(x => attachments.push(x) ) @@ -72,7 +72,7 @@ describe('AttachmentManager', () => { describe('with callback', () => { it('does not return a promise and adds the data and media', async function() { // Arrange - const attachments = [] + const attachments: IAttachment[] = [] const attachmentManager = new AttachmentManager(x => attachments.push(x) ) @@ -114,7 +114,7 @@ describe('AttachmentManager', () => { describe('without callback', () => { it('returns a promise and adds the data and media', async function() { // Arrange - const attachments = [] + const attachments: IAttachment[] = [] const attachmentManager = new AttachmentManager(x => attachments.push(x) ) @@ -153,7 +153,7 @@ describe('AttachmentManager', () => { describe('without media type', () => { it('throws', function() { // Arrange - const attachments = [] + const attachments: IAttachment[] = [] const attachmentManager = new AttachmentManager(x => attachments.push(x) ) @@ -182,7 +182,7 @@ describe('AttachmentManager', () => { describe('with media type', () => { it('adds the data and media', function() { // Arrange - const attachments = [] + const attachments: IAttachment[] = [] const attachmentManager = new AttachmentManager(x => attachments.push(x) ) @@ -207,7 +207,7 @@ describe('AttachmentManager', () => { describe('without mime type', () => { it('adds the data with the default mime type', function() { // Arrange - const attachments = [] + const attachments: IAttachment[] = [] const attachmentManager = new AttachmentManager(x => attachments.push(x) ) @@ -233,7 +233,7 @@ describe('AttachmentManager', () => { describe('unsupported data type', () => { it('throws', function() { // Arrange - const attachments = [] + const attachments: IAttachment[] = [] const attachmentManager = new AttachmentManager(x => attachments.push(x) ) diff --git a/src/runtime/helpers_spec.ts b/src/runtime/helpers_spec.ts index 3852e0067..668fe3986 100644 --- a/src/runtime/helpers_spec.ts +++ b/src/runtime/helpers_spec.ts @@ -11,12 +11,20 @@ describe('Helpers', () => { // Arrange const stepDefinitions = [ new StepDefinition({ - line: '3', + code: undefined, + expression: undefined, + id: '', + options: undefined, + line: 3, pattern: 'pattern1', uri: 'steps1.js', }), new StepDefinition({ - line: '4', + code: undefined, + expression: undefined, + id: '', + options: undefined, + line: 4, pattern: 'longer pattern2', uri: 'steps2.js', }), diff --git a/src/runtime/index.ts b/src/runtime/index.ts index 834658f33..db00634a8 100644 --- a/src/runtime/index.ts +++ b/src/runtime/index.ts @@ -60,7 +60,10 @@ export default class Runtime { this.success = true } - async runTestRunHooks(key: string, name: string): Promise { + async runTestRunHooks( + key: 'beforeTestRunHookDefinitions' | 'afterTestRunHookDefinitions', + name: string + ): Promise { if (this.options.dryRun) { return } diff --git a/src/runtime/parallel/slave.ts b/src/runtime/parallel/slave.ts index 784b3616f..3f8a99597 100644 --- a/src/runtime/parallel/slave.ts +++ b/src/runtime/parallel/slave.ts @@ -19,25 +19,34 @@ import { IRuntimeOptions } from '../index' const { uuid } = IdGenerator +type IExitFunction = (exitCode: number, error?: Error, message?: string) => void +type IMessageSender = (command: IMasterReport) => void + export default class Slave { private readonly cwd: string - private readonly exit: ( - exitCode: number, - error?: Error, - message?: string - ) => void + private readonly exit: IExitFunction private readonly id: string private readonly eventBroadcaster: EventEmitter private filterStacktraces: boolean private readonly newId: IdGenerator.NewId - private readonly sendMessage: (command: IMasterReport) => void + private readonly sendMessage: IMessageSender private readonly stackTraceFilter: StackTraceFilter private supportCodeLibrary: ISupportCodeLibrary private worldParameters: any private options: IRuntimeOptions - constructor({ cwd, exit, id, sendMessage }) { + constructor({ + cwd, + exit, + id, + sendMessage, + }: { + cwd: string + exit: IExitFunction + id: string + sendMessage: IMessageSender + }) { this.id = id this.newId = uuid() this.cwd = cwd diff --git a/src/runtime/pickle_runner_spec.ts b/src/runtime/pickle_runner_spec.ts index 0a8c75b3d..3a16d3ea4 100644 --- a/src/runtime/pickle_runner_spec.ts +++ b/src/runtime/pickle_runner_spec.ts @@ -7,11 +7,12 @@ import { messages } from 'cucumber-messages' import { incrementing } from 'cucumber-messages/dist/src/IdGenerator' import { parse } from '../../test/gherkin_helpers' import { buildSupportCodeLibrary } from '../../test/runtime_helpers' -import lolex from 'lolex' +import lolex, { InstalledClock } from 'lolex' import timeMethods, { millisecondsToDuration, getZeroDuration } from '../time' import { getBaseSupportCodeLibrary } from '../../test/fixtures/steps' import { ISupportCodeLibrary } from '../support_code_library_builder/types' import { valueOrDefault } from '../value_checker' +import IEnvelope = messages.IEnvelope interface ITestPickleRunnerRequest { gherkinDocument: messages.IGherkinDocument @@ -29,7 +30,7 @@ interface ITestPickleRunnerResponse { async function testPickleRunner( options: ITestPickleRunnerRequest ): Promise { - const envelopes = [] + const envelopes: IEnvelope[] = [] const eventBroadcaster = new EventEmitter() eventBroadcaster.on('envelope', e => envelopes.push(e)) const pickleRunner = new PickleRunner({ @@ -47,7 +48,7 @@ async function testPickleRunner( } describe('PickleRunner', () => { - let clock + let clock: InstalledClock beforeEach(() => { clock = lolex.install({ target: timeMethods }) diff --git a/src/stack_trace_filter.ts b/src/stack_trace_filter.ts index e277f0fcc..193b8a252 100644 --- a/src/stack_trace_filter.ts +++ b/src/stack_trace_filter.ts @@ -2,6 +2,7 @@ import _ from 'lodash' import stackChain from 'stack-chain' import path from 'path' import { valueOrDefault } from './value_checker' +import CallSite = NodeJS.CallSite const projectRootPath = path.join(__dirname, '..') const projectChildDirs = ['src', 'lib', 'node_modules'] @@ -13,34 +14,36 @@ export function isFileNameInCucumber(fileName: string): boolean { } export default class StackTraceFilter { - private currentFilter: any + private currentFilter: CallSite[] filter(): void { - this.currentFilter = stackChain.filter.attach((_err, frames) => { - if (this.isErrorInCucumber(frames)) { - return frames + this.currentFilter = stackChain.filter.attach( + (_err: any, frames: CallSite[]) => { + if (this.isErrorInCucumber(frames)) { + return frames + } + const index = _.findIndex(frames, this.isFrameInCucumber.bind(this)) + if (index === -1) { + return frames + } + return frames.slice(0, index) } - const index = _.findIndex(frames, this.isFrameInCucumber.bind(this)) - if (index === -1) { - return frames - } - return frames.slice(0, index) - }) + ) } - isErrorInCucumber(frames): boolean { + isErrorInCucumber(frames: CallSite[]): boolean { const filteredFrames = _.reject(frames, this.isFrameInNode.bind(this)) return ( filteredFrames.length > 0 && this.isFrameInCucumber(filteredFrames[0]) ) } - isFrameInCucumber(frame): boolean { + isFrameInCucumber(frame: CallSite): boolean { const fileName = valueOrDefault(frame.getFileName(), '') return isFileNameInCucumber(fileName) } - isFrameInNode(frame): boolean { + isFrameInNode(frame: CallSite): boolean { const fileName = valueOrDefault(frame.getFileName(), '') return !_.includes(fileName, path.sep) } diff --git a/src/support_code_library_builder/build_helpers.ts b/src/support_code_library_builder/build_helpers.ts index 42091fffd..7f53e4fda 100644 --- a/src/support_code_library_builder/build_helpers.ts +++ b/src/support_code_library_builder/build_helpers.ts @@ -6,6 +6,7 @@ import StackTrace from 'stacktrace-js' import { isFileNameInCucumber } from '../stack_trace_filter' import { doesHaveValue, doesNotHaveValue } from '../value_checker' import { ILineAndUri } from '../types' +import { IParameterTypeDefinition } from './types' export function getDefinitionLineAndUri(cwd: string): ILineAndUri { let line: number @@ -31,7 +32,7 @@ export function buildParameterType({ transformer, useForSnippets, preferForRegexpMatch, -}): ParameterType { +}: IParameterTypeDefinition): ParameterType { const getTypeName = deprecate( () => typeName, 'Cucumber defineParameterType: Use name instead of typeName' diff --git a/src/support_code_library_builder/index.ts b/src/support_code_library_builder/index.ts index dce0f3b52..5b141d269 100644 --- a/src/support_code_library_builder/index.ts +++ b/src/support_code_library_builder/index.ts @@ -22,6 +22,7 @@ import { TestCaseHookFunction, IDefineTestRunHookOptions, ISupportCodeLibrary, + IParameterTypeDefinition, } from './types' import World from './world' @@ -94,7 +95,7 @@ export class SupportCodeLibraryBuilder { } } - defineParameterType(options): void { + defineParameterType(options: IParameterTypeDefinition): void { const parameterType = buildParameterType(options) this.parameterTypeRegistry.defineParameterType(parameterType) } @@ -177,7 +178,13 @@ export class SupportCodeLibraryBuilder { } } - wrapCode({ code, wrapperOptions }): Function { + wrapCode({ + code, + wrapperOptions, + }: { + code: Function + wrapperOptions: any + }): Function { if (doesHaveValue(this.definitionFunctionWrapper)) { const codeLength = code.length const wrappedCode = this.definitionFunctionWrapper(code, wrapperOptions) diff --git a/src/support_code_library_builder/types.ts b/src/support_code_library_builder/types.ts index d4f4a9e41..c1312e015 100644 --- a/src/support_code_library_builder/types.ts +++ b/src/support_code_library_builder/types.ts @@ -35,8 +35,17 @@ export interface IDefineTestRunHookOptions { timeout?: number } +export interface IParameterTypeDefinition { + name: string + typeName: string + regexp: RegExp + transformer: (...match: string[]) => T + useForSnippets: boolean + preferForRegexpMatch: boolean +} + export interface IDefineSupportCodeMethods { - defineParameterType(options: any): void + defineParameterType(options: IParameterTypeDefinition): void defineStep(pattern: DefineStepPattern, code: Function): void defineStep( pattern: DefineStepPattern, diff --git a/src/support_code_library_builder/validate_arguments.ts b/src/support_code_library_builder/validate_arguments.ts index 677912e35..1984b9e80 100644 --- a/src/support_code_library_builder/validate_arguments.ts +++ b/src/support_code_library_builder/validate_arguments.ts @@ -1,5 +1,6 @@ import _, { Dictionary } from 'lodash' import { doesNotHaveValue } from '../value_checker' +import { DefineStepPattern, IDefineStepOptions } from './types' interface IValidation { identifier: string @@ -7,9 +8,15 @@ interface IValidation { predicate: (args: any) => boolean } +interface IDefineStepArguments { + pattern?: DefineStepPattern + options?: IDefineStepOptions + code?: Function +} + const optionsValidation = { expectedType: 'object or function', - predicate({ options }) { + predicate({ options }: IDefineStepArguments) { return _.isPlainObject(options) }, } @@ -17,14 +24,14 @@ const optionsValidation = { const optionsTimeoutValidation = { identifier: '"options.timeout"', expectedType: 'integer', - predicate({ options }) { + predicate({ options }: IDefineStepArguments) { return doesNotHaveValue(options.timeout) || _.isInteger(options.timeout) }, } const fnValidation = { expectedType: 'function', - predicate({ code }) { + predicate({ code }: IDefineStepArguments) { return _.isFunction(code) }, } @@ -61,7 +68,15 @@ const validations: Dictionary = { ], } -export default function validateArguments({ args, fnName, location }): void { +export default function validateArguments({ + args, + fnName, + location, +}: { + args?: IDefineStepArguments + fnName: string + location: string +}): void { validations[fnName].forEach(({ identifier, expectedType, predicate }) => { if (!predicate(args)) { throw new Error( diff --git a/src/support_code_library_builder/world.ts b/src/support_code_library_builder/world.ts index 40d68cf10..876244c80 100644 --- a/src/support_code_library_builder/world.ts +++ b/src/support_code_library_builder/world.ts @@ -1,10 +1,15 @@ import { ICreateAttachment } from '../runtime/attachment_manager' +export interface IWorldOptions { + attach: ICreateAttachment + parameters: any +} + export default class World { public readonly attach: ICreateAttachment public readonly parameters: any - constructor({ attach, parameters }) { + constructor({ attach, parameters }: IWorldOptions) { this.attach = attach this.parameters = parameters } diff --git a/src/time.ts b/src/time.ts index d0a17f682..dd3500869 100644 --- a/src/time.ts +++ b/src/time.ts @@ -5,7 +5,7 @@ export const NANOSECONDS_IN_MILLISECOND = 1e6 export const MILLISECONDS_IN_SECOND = 1e3 export const NANOSECONDS_IN_SECOND = 1e9 -let previousTimestamp +let previousTimestamp: number const methods: any = { beginTiming() { diff --git a/src/types/assertion-error-formatter/index.d.ts b/src/types/assertion-error-formatter/index.d.ts new file mode 100644 index 000000000..bf0e6b01a --- /dev/null +++ b/src/types/assertion-error-formatter/index.d.ts @@ -0,0 +1,3 @@ +declare module 'assertion-error-formatter' { + export function format(error: Error, options?: any): string +} diff --git a/src/types/duration/index.d.ts b/src/types/duration/index.d.ts new file mode 100644 index 000000000..667b4f0fa --- /dev/null +++ b/src/types/duration/index.d.ts @@ -0,0 +1,19 @@ +declare module 'duration' { + export default class Duration { + constructor(start: Date, end: Date) + get years(): number + get months(): number + get days(): number + get hours(): number + get minutes(): number + get seconds(): number + get milliseconds(): number + get month(): number + get day(): number + get hour(): number + get minute(): number + get second(): number + get millisecond(): number + toString(format: string): string + } +} diff --git a/src/types/is-generator.d.ts b/src/types/is-generator/index.d.ts similarity index 100% rename from src/types/is-generator.d.ts rename to src/types/is-generator/index.d.ts diff --git a/src/types/knuth-shuffle-seeded/index.d.ts b/src/types/knuth-shuffle-seeded/index.d.ts new file mode 100644 index 000000000..6f74f0500 --- /dev/null +++ b/src/types/knuth-shuffle-seeded/index.d.ts @@ -0,0 +1,3 @@ +declare module 'knuth-shuffle-seeded' { + export default function shuffle(inputArray: any[], seed?: any): any +} diff --git a/src/types/stack-chain/index.d.ts b/src/types/stack-chain/index.d.ts new file mode 100644 index 000000000..7aa6c2217 --- /dev/null +++ b/src/types/stack-chain/index.d.ts @@ -0,0 +1,4 @@ +declare module 'stack-chain' { + var _temp: any + export = _temp +} diff --git a/src/uncaught_exception_manager.ts b/src/uncaught_exception_manager.ts index e7be745b9..e68bf3135 100644 --- a/src/uncaught_exception_manager.ts +++ b/src/uncaught_exception_manager.ts @@ -1,7 +1,9 @@ +import UncaughtExceptionListener = NodeJS.UncaughtExceptionListener + declare var window: any // For browsers const UncaughtExceptionManager = { - registerHandler(handler): void { + registerHandler(handler: UncaughtExceptionListener): void { if (typeof window === 'undefined') { process.addListener('uncaughtException', handler) } else { @@ -9,7 +11,7 @@ const UncaughtExceptionManager = { } }, - unregisterHandler(handler): void { + unregisterHandler(handler: UncaughtExceptionListener): void { if (typeof window === 'undefined') { process.removeListener('uncaughtException', handler) } else { diff --git a/src/user_code_runner.ts b/src/user_code_runner.ts index 0e00d9450..8d5a96825 100644 --- a/src/user_code_runner.ts +++ b/src/user_code_runner.ts @@ -24,7 +24,7 @@ const UserCodeRunner = { timeoutInMilliseconds, }: IRunRequest): Promise { const callbackPromise = new Promise((resolve, reject) => { - argsArray.push((error, result) => { + argsArray.push((error: Error, result: IRunResponse) => { if (doesHaveValue(error)) { reject(error) } else { diff --git a/test/fixtures/json_formatter_steps.ts b/test/fixtures/json_formatter_steps.ts index 148965370..186473b77 100644 --- a/test/fixtures/json_formatter_steps.ts +++ b/test/fixtures/json_formatter_steps.ts @@ -2,8 +2,11 @@ import { buildSupportCodeLibrary } from '../runtime_helpers' import { ISupportCodeLibrary } from '../../src/support_code_library_builder/types' +import { InstalledClock } from 'lolex' -export function getJsonFormatterSupportCodeLibrary(clock): ISupportCodeLibrary { +export function getJsonFormatterSupportCodeLibrary( + clock: InstalledClock +): ISupportCodeLibrary { return buildSupportCodeLibrary(__dirname, ({ Given }) => { Given('a passing step', function() { clock.tick(1) diff --git a/test/fixtures/usage_steps.ts b/test/fixtures/usage_steps.ts index f8df8ecd2..52a4872e5 100644 --- a/test/fixtures/usage_steps.ts +++ b/test/fixtures/usage_steps.ts @@ -2,8 +2,11 @@ import { buildSupportCodeLibrary } from '../runtime_helpers' import { ISupportCodeLibrary } from '../../src/support_code_library_builder/types' +import { InstalledClock } from 'lolex' -export function getUsageSupportCodeLibrary(clock): ISupportCodeLibrary { +export function getUsageSupportCodeLibrary( + clock: InstalledClock +): ISupportCodeLibrary { return buildSupportCodeLibrary(__dirname, ({ Given }) => { Given('abc', function() { clock.tick(1) diff --git a/test/formatter_helpers.ts b/test/formatter_helpers.ts index 7aaa30b6a..5345f3745 100644 --- a/test/formatter_helpers.ts +++ b/test/formatter_helpers.ts @@ -10,6 +10,7 @@ import { ITestCaseAttempt } from '../src/formatter/helpers/event_data_collector' import { doesNotHaveValue } from '../src/value_checker' import { IParsedArgvFormatOptions } from '../src/cli/argv_parser' import { PassThrough } from 'stream' +import IEnvelope = messages.IEnvelope const { uuid } = IdGenerator @@ -59,7 +60,7 @@ export async function testFormatter({ stream: new PassThrough(), supportCodeLibrary, }) - let pickleIds = [] + let pickleIds: string[] = [] for (const source of sources) { const { pickles } = await generateEvents({ data: source.data, @@ -92,7 +93,7 @@ export async function getTestCaseAttempts({ } const eventBroadcaster = new EventEmitter() const eventDataCollector = new EventDataCollector(eventBroadcaster) - let pickleIds = [] + let pickleIds: string[] = [] for (const source of sources) { const { pickles } = await generateEvents({ data: source.data, @@ -125,9 +126,9 @@ export async function getEnvelopesAndEventDataCollector({ } const eventBroadcaster = new EventEmitter() const eventDataCollector = new EventDataCollector(eventBroadcaster) - const envelopes = [] + const envelopes: IEnvelope[] = [] eventBroadcaster.on('envelope', envelope => envelopes.push(envelope)) - let pickleIds = [] + let pickleIds: string[] = [] for (const source of sources) { const { pickles } = await generateEvents({ data: source.data, diff --git a/test/gherkin_helpers.ts b/test/gherkin_helpers.ts index 5a420b5d3..327c42a43 100644 --- a/test/gherkin_helpers.ts +++ b/test/gherkin_helpers.ts @@ -1,6 +1,7 @@ import Gherkin from 'gherkin' import { messages } from 'cucumber-messages' import { doesHaveValue } from '../src/value_checker' +import { EventEmitter } from 'events' export interface IParsedSource { pickles: messages.IPickle[] @@ -12,10 +13,15 @@ export interface IParsedSourceWithEnvelopes extends IParsedSource { envelopes: messages.IEnvelope[] } +export interface IParseRequest { + data: string + uri: string +} + export async function parse({ data, uri, -}): Promise { +}: IParseRequest): Promise { const sources = [ { source: { @@ -68,11 +74,17 @@ export async function parse({ }) } +export interface IGenerateEventsRequest { + data: string + eventBroadcaster: EventEmitter + uri: string +} + export async function generateEvents({ data, eventBroadcaster, uri, -}): Promise { +}: IGenerateEventsRequest): Promise { const { envelopes, source, gherkinDocument, pickles } = await parse({ data, uri, diff --git a/tsconfig.json b/tsconfig.json index cc75d28f0..6b9ac5b42 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,8 +3,14 @@ "esModuleInterop": true, "lib": ["es2017"], "module": "commonjs", + "noImplicitAny": true, + "noImplicitReturns": true, "noImplicitThis": true, "sourceMap": true, - "target": "es2017" + "target": "es2017", + "typeRoots": [ + "./node_modules/@types", + "./src/types" + ] }, -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index 5634fbf63..b6df9d46d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -236,11 +236,31 @@ resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.29.tgz#7cd933c902c4fc83046517a1bef973886d00bdb6" integrity sha512-kmVtnxTuUuhCET669irqQmPAez4KFnFVKvpleVRyfC3g+SHD1hIkFZcWLim9BVcwUBLO59o8VZE4yGCmTif8Yw== +"@types/chai-as-promised@*": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.2.tgz#2f564420e81eaf8650169e5a3a6b93e096e5068b" + integrity sha512-PO2gcfR3Oxa+u0QvECLe1xKXOqYTzCmWf0FhLhjREoW3fPAVamjihL7v1MOVLJLsnAMdLcjkfrs01yvDMwVK4Q== + dependencies: + "@types/chai" "*" + +"@types/chai@*", "@types/chai@^4.2.8": + version "4.2.8" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.8.tgz#c8d645506db0d15f4aafd4dfa873f443ad87ea59" + integrity sha512-U1bQiWbln41Yo6EeHMr+34aUhvrMVyrhn9lYfPSpLTCrZlGxU4Rtn1bocX+0p2Fc/Jkd2FanCEXdw0WNfHHM0w== + "@types/color-name@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== +"@types/dirty-chai@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@types/dirty-chai/-/dirty-chai-2.0.2.tgz#eeac4802329a41ed7815ac0c1a6360335bf77d0c" + integrity sha512-BruwIN/UQEU0ePghxEX+OyjngpOfOUKJQh3cmfeq2h2Su/g001iljVi3+Y2y2EFp3IPgjf4sMrRU33Hxv1FUqw== + dependencies: + "@types/chai" "*" + "@types/chai-as-promised" "*" + "@types/eslint-visitor-keys@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" @@ -251,6 +271,13 @@ resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== +"@types/fs-extra@^8.0.1": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.0.1.tgz#a2378d6e7e8afea1564e44aafa2e207dadf77686" + integrity sha512-J00cVDALmi/hJOYsunyT52Hva5TnJeKP5yd1r+mH/ZU0mbYZflR0Z5kw5kITtKTRYMhm1JMClOFYdHnQszEvqw== + dependencies: + "@types/node" "*" + "@types/glob@^7.1.1": version "7.1.1" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" @@ -270,6 +297,11 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.149.tgz#1342d63d948c6062838fbf961012f74d4e638440" integrity sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ== +"@types/lolex@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@types/lolex/-/lolex-5.1.0.tgz#11b4c4756c007306d0feeaf2f08f88350c635d2b" + integrity sha512-hCQ2dOEQUw1LwofdIpMMGGqENd5p5ANzvcTe1nXTjcQL84r7tcLXFJlBgi0Ggz0f7BLmE2epf0C5Q07iq2gV0g== + "@types/long@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.0.tgz#719551d2352d301ac8b81db732acb6bdc28dbdef" @@ -280,6 +312,16 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== +"@types/mocha@^7.0.1": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-7.0.1.tgz#5d7ec2a789a1f77c59b7ad071b9d50bf1abbfc9e" + integrity sha512-L/Nw/2e5KUaprNJoRA33oly+M8X8n0K+FwLTbYqwTcR14wdPWeRkigBLfSFpN/Asf9ENZTMZwLxjtjeYucAA4Q== + +"@types/mustache@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/mustache/-/mustache-4.0.0.tgz#8db01fdff5210646a9d8819ad2aca0ccb5fef12a" + integrity sha512-AVBOcLJenbpCIJcHUvGWj+YMlaiwcFlGK1YEH2mePXkB5B/vQLrFkHG9IpBH71mXnkbibc4V8Nnn1wJSxcgCEA== + "@types/mz@^2.7.0": version "2.7.0" resolved "https://registry.yarnpkg.com/@types/mz/-/mz-2.7.0.tgz#3ef27f457c4c3e8b197ca2670ee41d6f4effddf2" @@ -297,6 +339,38 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.13.tgz#ccebcdb990bd6139cd16e84c39dc2fb1023ca90c" integrity sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg== +"@types/progress@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/progress/-/progress-2.0.3.tgz#7ccbd9c6d4d601319126c469e73b5bb90dfc8ccc" + integrity sha512-bPOsfCZ4tsTlKiBjBhKnM8jpY5nmIll166IPD58D92hR7G7kZDfx5iB9wGF4NfZrdKolebjeAr3GouYkSGoJ/A== + dependencies: + "@types/node" "*" + +"@types/resolve@^1.14.0": + version "1.14.0" + resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.14.0.tgz#c95d696264f8e53e764a7c0b83e9317b458b76c3" + integrity sha512-bmjNBW6tok+67iOsASeYSJxSgY++BIR35nGyGLORTDirhra9reJ0shgGL3U7KPDUbOBCx8JrlCjd4d/y5uiMRQ== + dependencies: + "@types/node" "*" + +"@types/sinon-chai@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@types/sinon-chai/-/sinon-chai-3.2.3.tgz#afe392303dda95cc8069685d1e537ff434fa506e" + integrity sha512-TOUFS6vqS0PVL1I8NGVSNcFaNJtFoyZPXZ5zur+qlhDfOmQECZZM4H4kKgca6O8L+QceX/ymODZASfUfn+y4yQ== + dependencies: + "@types/chai" "*" + "@types/sinon" "*" + +"@types/sinon@*": + version "7.5.1" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-7.5.1.tgz#d27b81af0d1cfe1f9b24eebe7a24f74ae40f5b7c" + integrity sha512-EZQUP3hSZQyTQRfiLqelC9NMWd1kqLcmQE0dMiklxBkgi84T+cHOhnKpgk4NnOWpGX863yE6+IaGnOXUNFqDnQ== + +"@types/tmp@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.1.0.tgz#19cf73a7bcf641965485119726397a096f0049bd" + integrity sha512-6IwZ9HzWbCq6XoQWhxLpDjuADodH/MKXRUIDFudvgjcVdjFknvmR+DNsoUeer4XPrEnrZs04Jj+kfV9pFsrhmA== + "@types/uuid@^3.4.6": version "3.4.6" resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.6.tgz#d2c4c48eb85a757bf2927f75f939942d521e3016" @@ -304,6 +378,11 @@ dependencies: "@types/node" "*" +"@types/verror@^1.10.3": + version "1.10.3" + resolved "https://registry.yarnpkg.com/@types/verror/-/verror-1.10.3.tgz#8be16b0e750018fd4e2e4a766b8f2351d8b5df6a" + integrity sha512-7Jz0MPsW2pWg5dJfEp9nJYI0RDCYfgjg2wIo5HfQ8vOJvUq0/BxT7Mv2wNQvkKBmV9uT++6KF3reMnLmh/0HrA== + "@typescript-eslint/eslint-plugin@^2.0.0": version "2.13.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.13.0.tgz#57e933fe16a2fc66dbac059af0d6d85d921d748e"