Skip to content

Commit

Permalink
feat: refactor internals
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Jun 24, 2023
1 parent 12ce5d7 commit c57d344
Show file tree
Hide file tree
Showing 21 changed files with 3,127 additions and 746 deletions.
603 changes: 108 additions & 495 deletions index.ts

Large diffs are not rendered by default.

71 changes: 71 additions & 0 deletions modules/core/main.ts
@@ -0,0 +1,71 @@
/*
* @japa/runner
*
* (c) Japa
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import {
Emitter,
Refiner,
Test as BaseTest,
Suite as BaseSuite,
Group as BaseGroup,
Runner as BaseRunner,
TestContext as BaseTestContext,
} from '@japa/core'
import type { DataSetNode, TestHooksCleanupHandler } from '@japa/core/types'

export { Emitter, Refiner }

/**
* Test context carries context data for a given test.
*/
export class TestContext extends BaseTestContext {
/**
* Register a cleanup function that runs after the test finishes
* successfully or with an error.
*/
declare cleanup: (cleanupCallback: TestHooksCleanupHandler<TestContext>) => void

constructor(public test: Test) {
super()
this.cleanup = (cleanupCallback: TestHooksCleanupHandler<TestContext>) => {
test.cleanup(cleanupCallback)
}
}
}

/**
* Test class represents an individual test and exposes API to tweak
* its runtime behavior.
*/
export class Test<TestData extends DataSetNode = undefined> extends BaseTest<
TestContext,
TestData
> {
/**
* @inheritdoc
*/
static disposeCallbacks = []
}

/**
* TestGroup is used to bulk configure a collection of tests and
* define lifecycle hooks for them
*/
export class Group extends BaseGroup<TestContext> {}

/**
* A suite is a collection of tests created around a given
* testing type. For example: A suite for unit tests, a
* suite for functional tests and so on.
*/
export class Suite extends BaseSuite<TestContext> {}

/**
* Runner class is used to execute the tests
*/
export class Runner extends BaseRunner<TestContext> {}
31 changes: 24 additions & 7 deletions package.json
Expand Up @@ -14,11 +14,13 @@
},
"exports": {
".": "./build/index.js",
"./types": "./build/src/types.js"
"./types": "./build/src/types.js",
"./core": "./build/modules/core/main.js"
},
"scripts": {
"pretest": "npm run lint",
"test": "glob -c \"node --loader=ts-node/esm --test\" \"test/**/*.spec.ts\"",
"test": "cross-env NODE_ENV=japa:runner c8 npm run quick:test",
"quick:test": "glob -c \"node --enable-source-maps --loader=ts-node/esm --test\" \"tests/*.spec.ts\"",
"clean": "del build",
"typecheck": "tsc --noEmit",
"compile": "npm run lint && npm run clean && tsc",
Expand All @@ -45,11 +47,17 @@
"@commitlint/cli": "^17.6.5",
"@commitlint/config-conventional": "^17.6.5",
"@swc/core": "^1.3.66",
"@types/chai": "^4.3.5",
"@types/chai-subset": "^1.3.3",
"@types/node": "^20.3.1",
"c8": "^8.0.0",
"chai": "^4.3.7",
"chai-subset": "^1.6.0",
"cross-env": "^7.0.3",
"del-cli": "^5.0.0",
"eslint": "^8.36.0",
"github-label-sync": "^2.3.1",
"glob": "^10.3.0",
"husky": "^8.0.3",
"japa": "^4.0.0",
"np": "^8.0.4",
Expand All @@ -58,13 +66,12 @@
"typescript": "^5.1.3"
},
"dependencies": {
"@japa/core": "^7.3.3",
"@japa/errors-printer": "^2.1.0",
"@poppinss/cliui": "^3.0.5",
"@japa/core": "^8.0.0-1",
"@japa/errors-printer": "^3.0.0-0",
"@poppinss/cliui": "^6.1.1-2",
"@poppinss/hooks": "^7.1.1-3",
"fast-glob": "^3.2.12",
"getopts": "^2.3.0",
"inclusion": "^1.0.1"
"getopts": "^2.3.0"
},
"directories": {
"test": "test"
Expand Down Expand Up @@ -95,5 +102,15 @@
"prettier": "@adonisjs/prettier-config",
"eslintConfig": {
"extends": "@adonisjs/eslint-config/package"
},
"c8": {
"reporter": [
"text",
"html"
],
"exclude": [
"tests/**",
"tests_helpers/**"
]
}
}
87 changes: 87 additions & 0 deletions src/cli_parser.ts
@@ -0,0 +1,87 @@
/*
* @japa/runner
*
* (c) Japa
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

// @ts-ignore-error
import getopts from 'getopts'
import { colors } from '@poppinss/cliui'
import type { CLIArgs } from './types.js'

const ansi = colors.ansi()

/**
* Known commandline options. The user can still define additional flags and they
* will be parsed aswell, but without any normalization
*/
const OPTIONS = {
string: ['tests', 'groups', 'tags', 'files', 'timeout', 'retries', 'reporter'],
boolean: ['forceExit', 'help', 'matchAll'],
alias: {
forceExit: 'force-exit',
matchAll: 'match-all',
help: 'h',
},
}

/**
* Help string to display when the `--help flag is used`
*/
const GET_HELP = () => `
${ansi.yellow('@japa/runner v2.3.0')}
${ansi.green('--tests')} ${ansi.dim('Filter by test titles')}
${ansi.green('--groups')} ${ansi.dim('Filter by group titles')}
${ansi.green('--tags')} ${ansi.dim('Filter by test tags')}
${ansi.green('--files')} ${ansi.dim('Filter by tests file name')}
${ansi.green('--force-exit')} ${ansi.dim('Forcefully exit the process')}
${ansi.green('--timeout')} ${ansi.dim('Define global timeout for tests')}
${ansi.green('--retries')} ${ansi.dim('Define global retries for tests')}
${ansi.green('--reporter')} ${ansi.dim('Define reporter(s) to use')}
${ansi.green('-h, --help')} ${ansi.dim('View commandline help')}
${ansi.yellow('Examples:')}
${ansi.dim('node bin/test.js --tags="@github"')}
${ansi.dim('node bin/test.js --tags="~@github"')}
${ansi.dim('node bin/test.js --tags="@github,@slow,@integration" --match-all')}
${ansi.dim('node bin/test.js --force-exit')}
${ansi.dim('node bin/test.js --files="user"')}
${ansi.dim('node bin/test.js --files="functional/user"')}
${ansi.dim('node bin/test.js --files="unit/user"')}
${ansi.yellow('Notes:')}
- When groups and tests filters are applied together. We will first filter the
tests by group title and then apply the tests title filter.
- The timeout defined on test object takes precedence over the ${ansi.green('--timeout')} flag.
- The retries defined on test object takes precedence over the ${ansi.green('--retries')} flag.
- The ${ansi.green('--files')} flag checks for the file names ending with the filter substring.
`

/**
* CLI Parser is used to parse the commandline argument
*/
export class CliParser {
#argv: string[]

constructor(argv: string[]) {
this.#argv = argv
}

/**
* Parses command-line arguments
*/
parse(): CLIArgs {
return getopts(this.#argv, OPTIONS)
}

/**
* Returns the help string
*/
getHelp() {
return GET_HELP()
}
}
170 changes: 170 additions & 0 deletions src/config_manager.ts
@@ -0,0 +1,170 @@
/*
* @japa/runner
*
* (c) Japa
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import debug from './debug.js'
import { Refiner } from '../modules/core/main.js'
import type { CLIArgs, Config, Filters } from './types.js'

/**
* Defaults to use for configuration
*/
const DEFAULTS = {
files: [],
timeout: 2000,
retries: 1,
forceExit: false,
plugins: [],
reporters: {
activated: ['spec'],
list: [{ name: 'spec', handler: {} as any }],
},
importer: (filePath) => import(filePath.href),
configureSuite: () => {},
} satisfies Config

/**
* Config manager is used to hydrate the configuration by merging
* the defaults, user defined config and the command line
* flags.
*
* The command line flags have the upmost priority
*/
export class ConfigManager {
#config: Config
#cliArgs: CLIArgs

constructor(config: Config, cliArgs: CLIArgs) {
this.#config = config
this.#cliArgs = cliArgs
}

/**
* Processes a CLI argument and converts it to an
* array of strings
*/
#processAsArray(value: string | string[]): string[] {
return Array.isArray(value) ? value : value.split(',').map((item: string) => item.trim())
}

/**
* Returns a copy of filters based upon the CLI
* arguments.
*/
#getCLIFilters(): Filters {
const filters: Filters = {}

if (this.#cliArgs.tags) {
filters.tags = this.#processAsArray(this.#cliArgs.tags)
}
if (this.#cliArgs.tests) {
filters.tests = this.#processAsArray(this.#cliArgs.tests)
}
if (this.#cliArgs.files) {
filters.files = this.#processAsArray(this.#cliArgs.files)
}
if (this.#cliArgs.groups) {
filters.groups = this.#processAsArray(this.#cliArgs.groups)
}
if (this.#cliArgs._ && this.#cliArgs._.length) {
filters.suites = this.#processAsArray(this.#cliArgs._)
}

return filters
}

/**
* Returns the timeout from the CLI args
*/
#getCLITimeout(): number | undefined {
if (this.#cliArgs.timeout) {
const value = Number(this.#cliArgs.timeout)
if (!Number.isNaN(value)) {
return value
}
}
}

/**
* Returns the retries from the CLI args
*/
#getCLIRetries(): number | undefined {
if (this.#cliArgs.retries) {
const value = Number(this.#cliArgs.retries)
if (!Number.isNaN(value)) {
return value
}
}
}

/**
* Returns reporters selected using the commandline
* --reporter flag
*/
#getCLIReporters(): string[] | undefined {
if (this.#cliArgs.reporter) {
return this.#processAsArray(this.#cliArgs.reporter)
}
}

/**
* Hydrates the config with user defined options and the
* command-line flags.
*/
hydrate(): Required<Config> {
const cliFilters = this.#getCLIFilters()
const cliRetries = this.#getCLIRetries()
const cliTimeout = this.#getCLITimeout()
const cliReporters = this.#getCLIReporters()

debug('filters applied using CLI flags %O', cliFilters)

const baseConfig: Omit<Required<Config>, 'files' | 'suites'> = {
cwd: this.#config.cwd ?? process.cwd(),
filters: Object.assign({}, this.#config.filters ?? {}, cliFilters),
importer: this.#config.importer ?? DEFAULTS.importer,
refiner: this.#config.refiner ?? new Refiner(),
retries: cliRetries ?? this.#config.retries ?? DEFAULTS.retries,
timeout: cliTimeout ?? this.#config.timeout ?? DEFAULTS.timeout,
plugins: this.#config.plugins ?? DEFAULTS.plugins,
forceExit: this.#config.forceExit ?? DEFAULTS.forceExit,
reporters: this.#config.reporters ?? DEFAULTS.reporters,
configureSuite: this.#config.configureSuite ?? DEFAULTS.configureSuite,
setup: this.#config.setup || [],
teardown: this.#config.teardown || [],
}

/**
* Overwrite activated reporters when defined using CLI
* flag
*/
if (cliReporters) {
baseConfig.reporters.activated = cliReporters
}

if ('files' in this.#config) {
return {
files: this.#config.files,
...baseConfig,
}
}

return {
suites: this.#config.suites.map((suite) => {
return {
name: suite.name,
files: suite.files,
timeout: cliTimeout ?? suite.timeout ?? baseConfig.timeout,
retries: cliRetries ?? suite.retries ?? baseConfig.retries,
configure: suite.configure,
}
}),
...baseConfig,
}
}
}

0 comments on commit c57d344

Please sign in to comment.