UNPKG

codeceptjs

Version:

Supercharged End 2 End Testing Framework for NodeJS

253 lines (213 loc) 7.83 kB
const fs = require('fs') const path = require('path') const { getConfig, getTestRoot } = require('./utils') const Codecept = require('../codecept') const container = require('../container') const output = require('../output') const actingHelpers = [...container.STANDARD_ACTING_HELPERS, 'REST'] /** * Prepare data and generate content of definitions file * @private * * @param {Object} params * @param {boolean} params.hasCustomStepsFile * @param {boolean} params.hasCustomHelper * @param {Map} params.supportObject * @param {Array<string>} params.helperNames * @param {Array<string>} params.importPaths * @param params.translations * * @returns {string} */ const getDefinitionsFileContent = ({ hasCustomHelper, hasCustomStepsFile, helperNames, supportObject, importPaths, translations }) => { const getHelperListFragment = ({ hasCustomHelper, hasCustomStepsFile }) => { if (hasCustomHelper && hasCustomStepsFile) { return `${['ReturnType<steps_file>', 'WithTranslation<Methods>'].join(', ')}` } if (hasCustomStepsFile) { return 'ReturnType<steps_file>' } return 'WithTranslation<Methods>' } const helpersListFragment = getHelperListFragment({ hasCustomHelper, hasCustomStepsFile, }) const importPathsFragment = importPaths.join('\n') const supportObjectsTypeFragment = convertMapToType(supportObject) const methodsTypeFragment = helperNames.length > 0 ? `interface Methods extends ${helperNames.join(', ')} {}` : '' const translatedActionsFragment = JSON.stringify(translations.vocabulary.actions, null, 2) return generateDefinitionsContent({ helpersListFragment, importPathsFragment, supportObjectsTypeFragment, methodsTypeFragment, translatedActionsFragment, }) } /** * Generate content for definitions file from fragments * @private * * @param {Object} fragments - parts for template * @param {string} fragments.importPathsFragment * @param {string} fragments.supportObjectsTypeFragment * @param {string} fragments.methodsTypeFragment * @param {string} fragments.helpersListFragment * @param {string} fragments.translatedActionsFragment * * @returns {string} */ const generateDefinitionsContent = ({ importPathsFragment, supportObjectsTypeFragment, methodsTypeFragment, helpersListFragment, translatedActionsFragment }) => { return `/// <reference types='codeceptjs' /> ${importPathsFragment} declare namespace CodeceptJS { interface SupportObject ${supportObjectsTypeFragment} ${methodsTypeFragment} interface I extends ${helpersListFragment} {} namespace Translation { interface Actions ${translatedActionsFragment} } } ` } /** @type {Array<string>} */ const helperNames = [] /** @type {Array<string>} */ const customHelpers = [] module.exports = function (genPath, options) { const configFile = options.config || genPath /** @type {string} */ const testsPath = getTestRoot(configFile) const config = getConfig(configFile) if (!config) return /** @type {Object<string, string>} */ const helperPaths = {} /** @type {Object<string, string>} */ const supportPaths = {} /** @type {boolean} */ let hasCustomStepsFile = false /** @type {boolean} */ let hasCustomHelper = false /** @type {string} */ const targetFolderPath = (options.output && getTestRoot(options.output)) || testsPath const codecept = new Codecept(config, {}) codecept.init(testsPath) const helpers = container.helpers() const translations = container.translation() for (const name in helpers) { const require = codecept.config.helpers[name].require if (require) { helperPaths[name] = require helperNames.push(name) } else { const fullBasedPromised = codecept.config.fullPromiseBased helperNames.push(fullBasedPromised === true ? `${name}Ts` : name) } if (!actingHelpers.includes(name)) { customHelpers.push(name) } } let autoLogin if (config.plugins.autoLogin) { autoLogin = config.plugins.autoLogin.inject } const supportObject = new Map() supportObject.set('I', 'I') supportObject.set('current', 'any') if (translations.loaded) { supportObject.set(translations.I, translations.I) } if (autoLogin) { supportObject.set(autoLogin, 'any') } if (customHelpers.length > 0) { hasCustomHelper = true } for (const name in codecept.config.include) { const includePath = codecept.config.include[name] if (name === 'I' || name === translations.I) { hasCustomStepsFile = true supportPaths.steps_file = includePath continue } supportPaths[name] = includePath supportObject.set(name, name) } let definitionsFileContent = getDefinitionsFileContent({ helperNames, supportObject, importPaths: getImportString(testsPath, targetFolderPath, supportPaths, helperPaths), translations, hasCustomStepsFile, hasCustomHelper, }) // add aliases for translations if (translations.loaded) { const namespaceTranslationAliases = [] namespaceTranslationAliases.push(`interface ${translations.vocabulary.I} extends WithTranslation<Methods> {}`) namespaceTranslationAliases.push(' namespace Translation {') definitionsFileContent = definitionsFileContent.replace('namespace Translation {', namespaceTranslationAliases.join('\n')) const translationAliases = [] if (translations.vocabulary.contexts) { Object.keys(translations.vocabulary.contexts).forEach(k => { translationAliases.push(`declare const ${translations.vocabulary.contexts[k]}: typeof ${k};`) }) } definitionsFileContent += `\n${translationAliases.join('\n')}` } if (options.dryRun) return fs.writeFileSync(path.join(targetFolderPath, 'steps.d.ts'), definitionsFileContent) output.print('TypeScript Definitions provide autocompletion in Visual Studio Code and other IDEs') output.print('Definitions were generated in steps.d.ts') } /** * Returns the relative path from the to the targeted folder. * @param {string} originalPath * @param {string} targetFolderPath * @param {string} testsPath */ function getPath(originalPath, targetFolderPath, testsPath) { const parsedPath = path.parse(originalPath) // Remove typescript extension if exists. if (parsedPath.base.endsWith('.d.ts')) parsedPath.base = parsedPath.base.substring(0, parsedPath.base.length - 5) else if (parsedPath.ext === '.ts') parsedPath.base = parsedPath.name if (!parsedPath.dir.startsWith('.')) return path.posix.join(parsedPath.dir, parsedPath.base) const relativePath = path.posix.relative( targetFolderPath.split(path.sep).join(path.posix.sep), path.posix.join(testsPath.split(path.sep).join(path.posix.sep), parsedPath.dir.split(path.sep).join(path.posix.sep), parsedPath.base.split(path.sep).join(path.posix.sep)), ) return relativePath.startsWith('.') ? relativePath : `./${relativePath}` } /** * * * @param {string} testsPath * @param {string} targetFolderPath * @param {Object<string, string>} pathsToType * @param {Object<string, string>} pathsToValue * * @returns {Array<string>} */ function getImportString(testsPath, targetFolderPath, pathsToType, pathsToValue) { const importStrings = [] for (const name in pathsToType) { const relativePath = getPath(pathsToType[name], targetFolderPath, testsPath) importStrings.push(`type ${name} = typeof import('${relativePath}');`) } for (const name in pathsToValue) { const relativePath = getPath(pathsToValue[name], targetFolderPath, testsPath) importStrings.push(`type ${name} = import('${relativePath}');`) } return importStrings } /** * @param {Map} map * * @returns {string} */ function convertMapToType(map) { return `{ ${Array.from(map) .map(([key, value]) => `${key}: ${value}`) .join(', ')} }` }