//{{HEADER COMMENT START}} // This file is automatically generated, edits will be lost on rebuild //{{HEADER COMMENT END}} import { parseTestArgs, PromiseWithSubtest, TapPlugin, TestArgs, TestBase, TestBaseOpts, } from '@tapjs/core' //{{PLUGIN IMPORT START}} import * as Plugin_after from "@tapjs/after" import * as Plugin_afterEach from "@tapjs/after-each" import * as Plugin_asserts from "@tapjs/asserts" import * as Plugin_before from "@tapjs/before" import * as Plugin_beforeEach from "@tapjs/before-each" import * as Plugin_chdir from "@tapjs/chdir" import * as Plugin_filter from "@tapjs/filter" import * as Plugin_fixture from "@tapjs/fixture" import * as Plugin_intercept from "@tapjs/intercept" import * as Plugin_mock from "@tapjs/mock" import * as Plugin_nodeSerialize from "@tapjs/node-serialize" import * as Plugin_snapshot from "@tapjs/snapshot" import * as Plugin_spawn from "@tapjs/spawn" import * as Plugin_stdin from "@tapjs/stdin" import * as Plugin_typescript from "@tapjs/typescript" import * as Plugin_worker from "@tapjs/worker" //{{PLUGIN IMPORT END}} import type { ConfigSet, ConfigSetFromMetaSet, Jack, ValidValue, } from 'jackspeak' import { isConfigOption } from 'jackspeak' import { inspect } from 'node:util' /** * The set of file extensions that the tap runner will load * by default. Expaned into the `include` config values if they * contain the token `__EXTENSIONS__`. * * If plugins export a `testFileExtensions` string array, then the * entries will be added to this set. */ export const testFileExtensions = new Set(['js', 'cjs', 'mjs', 'tap']) //{{FILE TYPES START}} testFileExtensions.add("cts") testFileExtensions.add("jsx") testFileExtensions.add("mts") testFileExtensions.add("ts") testFileExtensions.add("tsx") //{{FILE TYPES END}} const kInspect = Symbol.for('nodejs.util.inspect.custom') const copyInspect = (v: Function) => ({ [kInspect]: (...args: any[]) => inspect(v, ...args), }) const copyToString = (v: Function) => ({ toString: Object.assign(() => v.toString(), { toString: () => 'function toString() { [native code] }', }), }) const copyFunction = ( t: Test, plug: Plug, v: Function, ) => { const f: (this: Plug, ...args: any) => any = function ( ...args: any[] ) { // if you do `const { method } = t` then calling `method` will // call it on the plugin that provided it. // So no need to pre-bind anything, really. const thisArg = this === t || this === undefined ? plug : this const ret = v.apply(thisArg, args) // If a plugin method returns 'this', and it's the plugin, // then we return the extended Test instead. return ret === thisArg && thisArg === plug ? t : ret } const vv = Object.assign( Object.assign(f, v), copyToString(v), copyInspect(v), ) vv.prototype = v.prototype const nameProp = Reflect.getOwnPropertyDescriptor(v, 'name') if (nameProp) { Reflect.defineProperty(f, 'name', nameProp) } return vv } /** * Utility type to combine the array of plugins into a single combined * return type. */ export type PluginResult

any)[]> = P extends ( [ infer H extends (t: TestBase, opts: any) => any, ...infer T extends ((t: TestBase, opts: any) => any)[], ] ) ? ReturnType & PluginResult : {} /** * The union of return types of an array of functions */ type AnyReturnValue any)[]> = A extends ( [ infer H extends (...a: any[]) => any, ...infer T extends ((...a: any[]) => any)[], ] ) ? ReturnType | AnyReturnValue : never type Plug = | TestBase | { t: Test pluginLoaded( plugin: (t: any, opts?: any) => T, ): boolean plugins: TapPlugin[] } | AnyReturnValue type Plugged = TestBase & { t: Test } & BuiltPlugins type PlugKeys = keyof Plugged /** * Utility type to get the second parameter of a function, used to * get the types of all plugin options. */ export type SecondParam = T extends [any, infer S] ? S : unknown /** * The union of the second parameters of all loaded plugin methods */ export type PluginOpts

any)[]> = P extends ( [ infer H extends (t: TestBase, opts: any) => any, ...infer T extends ((t: TestBase, opts: any) => any)[], ] ) ? SecondParam> & PluginOpts : {} /** * Options that may be provided to `t.test()`. Extends * {@link @tapjs/core!index.Extra}, {@link @tapjs/core!base.BaseOpts}, * {@link @tapjs/core!test-base.TestBaseOpts}, and the second argument to all * plugin methods currently in use. */ export type TestOpts = TestBaseOpts & PluginOpts let plugins_: PluginSet /** * Type that is the array of all plugin functions loaded */ //{{PLUGINS CODE START}} // export type PluginSet = (TapPlugin | TapPlugin)[] // const plugins = () => { // if (plugins_) return plugins_ // return (plugins_ = []) // } export type PluginSet = [ typeof Plugin_after.plugin, typeof Plugin_afterEach.plugin, typeof Plugin_asserts.plugin, typeof Plugin_before.plugin, typeof Plugin_beforeEach.plugin, typeof Plugin_chdir.plugin, typeof Plugin_filter.plugin, typeof Plugin_fixture.plugin, typeof Plugin_intercept.plugin, typeof Plugin_mock.plugin, typeof Plugin_nodeSerialize.plugin, typeof Plugin_snapshot.plugin, typeof Plugin_spawn.plugin, typeof Plugin_stdin.plugin, typeof Plugin_typescript.plugin, typeof Plugin_worker.plugin, ] const plugins = () => { if (plugins_) return plugins_ return (plugins_ = [ Plugin_after.plugin, Plugin_afterEach.plugin, Plugin_asserts.plugin, Plugin_before.plugin, Plugin_beforeEach.plugin, Plugin_chdir.plugin, Plugin_filter.plugin, Plugin_fixture.plugin, Plugin_intercept.plugin, Plugin_mock.plugin, Plugin_nodeSerialize.plugin, Plugin_snapshot.plugin, Plugin_spawn.plugin, Plugin_stdin.plugin, Plugin_typescript.plugin, Plugin_worker.plugin, ]) } //{{PLUGINS CODE END}} /** * The combined configuration object generated by the `config` * objects exported by plugins. */ //{{PLUGINS CONFIG START}} // // just referenced to keep prettier/tslint happy // /* c8 ignore start */ // isConfigOption // const c = (j: Jack) => j // const cs = c as unknown as ValidValue & // ConfigSetFromMetaSet<'boolean', false, { x: {} }> // c // cs // /* c8 ignore stop */ export const execArgv = ( values: ReturnType['parse']>['values'] ) => { const argv = [] const config_Plugin_filter_0 = Plugin_filter.config["only"] const config_Plugin_filter_0_value = values["only"] const config_Plugin_filter_1 = Plugin_filter.config["grep"] const config_Plugin_filter_1_value = values["grep"] if (config_Plugin_filter_0_value !== undefined) { argv.push(...config_Plugin_filter_0.nodeArgs(config_Plugin_filter_0_value)) } if (config_Plugin_filter_1_value !== undefined) { argv.push(...config_Plugin_filter_1.nodeArgs(config_Plugin_filter_1_value)) } return argv } export const config = (jack: Jack) => { const config_Plugin_filter_0 = Plugin_filter.config["only"] if (!isConfigOption(config_Plugin_filter_0, "boolean", false)) { throw new Error("Invalid config option 'only' defined in plugin: '@tapjs/filter'") } const config_Plugin_filter_1 = Plugin_filter.config["grep"] if (!isConfigOption(config_Plugin_filter_1, "string", true)) { throw new Error("Invalid config option 'grep' defined in plugin: '@tapjs/filter'") } const config_Plugin_filter_2 = Plugin_filter.config["invert"] if (!isConfigOption(config_Plugin_filter_2, "boolean", false)) { throw new Error("Invalid config option 'invert' defined in plugin: '@tapjs/filter'") } const config_Plugin_filter_3 = Plugin_filter.config["no-invert"] if (!isConfigOption(config_Plugin_filter_3, "boolean", false)) { throw new Error("Invalid config option 'no-invert' defined in plugin: '@tapjs/filter'") } const config_Plugin_filter_4 = Plugin_filter.config["filter-quietly"] if (!isConfigOption(config_Plugin_filter_4, "boolean", false)) { throw new Error("Invalid config option 'filter-quietly' defined in plugin: '@tapjs/filter'") } const config_Plugin_filter_5 = Plugin_filter.config["no-filter-quietly"] if (!isConfigOption(config_Plugin_filter_5, "boolean", false)) { throw new Error("Invalid config option 'no-filter-quietly' defined in plugin: '@tapjs/filter'") } const config_Plugin_fixture_0 = Plugin_fixture.config["save-fixture"] if (!isConfigOption(config_Plugin_fixture_0, "boolean", false)) { throw new Error("Invalid config option 'save-fixture' defined in plugin: '@tapjs/fixture'") } const config_Plugin_snapshot_0 = Plugin_snapshot.config["snapshot"] if (!isConfigOption(config_Plugin_snapshot_0, "boolean", false)) { throw new Error("Invalid config option 'snapshot' defined in plugin: '@tapjs/snapshot'") } const config_Plugin_snapshot_1 = Plugin_snapshot.config["snapshot-clean-cwd"] if (!isConfigOption(config_Plugin_snapshot_1, "boolean", false)) { throw new Error("Invalid config option 'snapshot-clean-cwd' defined in plugin: '@tapjs/snapshot'") } const config_Plugin_snapshot_2 = Plugin_snapshot.config["no-snapshot-clean-cwd"] if (!isConfigOption(config_Plugin_snapshot_2, "boolean", false)) { throw new Error("Invalid config option 'no-snapshot-clean-cwd' defined in plugin: '@tapjs/snapshot'") } const config_Plugin_typescript_0 = Plugin_typescript.config["typecheck"] if (!isConfigOption(config_Plugin_typescript_0, "boolean", false)) { throw new Error("Invalid config option 'typecheck' defined in plugin: '@tapjs/typescript'") } const config_Plugin_typescript_1 = Plugin_typescript.config["tsconfig"] if (!isConfigOption(config_Plugin_typescript_1, "string", false)) { throw new Error("Invalid config option 'tsconfig' defined in plugin: '@tapjs/typescript'") } const config_Plugin_typescript_2 = Plugin_typescript.config["type-strip-only"] if (!isConfigOption(config_Plugin_typescript_2, "boolean", false)) { throw new Error("Invalid config option 'type-strip-only' defined in plugin: '@tapjs/typescript'") } return jack .heading("From plugin: @tapjs/filter") .flag({ "only": config_Plugin_filter_0 }) .optList({ "grep": config_Plugin_filter_1 }) .flag({ "invert": config_Plugin_filter_2 }) .flag({ "no-invert": config_Plugin_filter_3 }) .flag({ "filter-quietly": config_Plugin_filter_4 }) .flag({ "no-filter-quietly": config_Plugin_filter_5 }) .heading("From plugin: @tapjs/fixture") .flag({ "save-fixture": config_Plugin_fixture_0 }) .heading("From plugin: @tapjs/snapshot") .flag({ "snapshot": config_Plugin_snapshot_0 }) .flag({ "snapshot-clean-cwd": config_Plugin_snapshot_1 }) .flag({ "no-snapshot-clean-cwd": config_Plugin_snapshot_2 }) .heading("From plugin: @tapjs/typescript") .flag({ "typecheck": config_Plugin_typescript_0 }) .opt({ "tsconfig": config_Plugin_typescript_1 }) .flag({ "type-strip-only": config_Plugin_typescript_2 }) } //{{PLUGINS CONFIG END}} //{{LOADERS START}} // // these are always added with --loader // export const loaders = [] // // these are added with --import, if available // export const importLoaders = [] // // these are added with --loader, only if --import is unavailable // export const loaderFallbacks = [] const preloaders = new Set([ "@tapjs/typescript/esm" ]) const preimports = new Set([ "@tapjs/typescript/import" ]) /** * The set of `loader` strings exported by plugins. If a plugin exports * `preload = true`, then it will be sorted to the start of this list, so * that Node loads it before other loaders. */ export const loaders: string[] = [].sort( (a, b) => preloaders.has(a) && !preloaders.has(b) ? -1 : !preloaders.has(a) && preloaders.has(b) ? 1 : 0 ) /** * The set of `importLoader` strings exported by plugins, for use with * `Module.register` in node v20.6 and higher. */ export const importLoaders: string[] = [ "@tapjs/mock/import", "@tapjs/typescript/import" ].sort( (a, b) => preimports.has(a) && !preimports.has(b) ? -1 : !preimports.has(a) && preimports.has(b) ? 1 : 0 ) /** * All `loader` strings exported by plugins, including fallbacks provided * for those that also export an `importLoader` */ export const loaderFallbacks: string[] = [ "@tapjs/mock/loader", "@tapjs/typescript/esm" ].sort( (a, b) => preloaders.has(a) && !preloaders.has(b) ? -1 : !preloaders.has(a) && preloaders.has(b) ? 1 : 0 ) //{{LOADERS END}} /** * The string signature that lists all loaded plugins alphabetically, used * to determine whether a rebuild is necessary by comparing it to the `plugin` * config value. */ //{{PLUGIN SIGNATURE START}} // export const signature = '' export const signature = `@tapjs/after @tapjs/after-each @tapjs/asserts @tapjs/before @tapjs/before-each @tapjs/chdir @tapjs/filter @tapjs/fixture @tapjs/intercept @tapjs/mock @tapjs/node-serialize @tapjs/snapshot @tapjs/spawn @tapjs/stdin @tapjs/typescript @tapjs/worker` //{{PLUGIN SIGNATURE END}} /** * Union of {@link @tapjs/core!test-base.TestBase} plus all plugin * return values */ export type TTest

= TestBase & PluginResult

/** * Interface that is the assembled result of every loaded plugin. * * This is extended into an interface because otherwise the code * hinting is overwhelmingly extravagant. */ export interface BuiltPlugins extends PluginResult {} const applyPlugins = ( base: Test, plugs: (TapPlugin | TapPlugin)[] = plugins() as ( | TapPlugin | TapPlugin )[], ): Test & Ext => { const ext: Plug[] = plugs // typecast in case we have *only* option-less plugins. .map(p => (p as TapPlugin, TestBaseOpts>)(base, base.options), ) .concat(base) const getCache = new Map() // extend the proxy with Object.create, and then set the toStringTag // to 'Test', so we don't get stack frames like `Proxy.` const t = Object.create( new Proxy(base, { has(_, p) { for (const t of ext) { if (Reflect.has(t, p)) return true } return false }, ownKeys() { const k: PlugKeys[] = [] for (const t of ext) { const keys = Reflect.ownKeys(t) as PlugKeys[] k.push(...keys) } return [...new Set(k)] }, set(_, p, v) { // check to see if there's any setters, and if so, set it there // otherwise, just set on the base let didSet = false if (getCache.has(p)) getCache.delete(p) for (const t of ext) { let o: Object | null = t while (o) { // assign to the all plugs that can receive it const prop = Reflect.getOwnPropertyDescriptor(o, p) if (prop) { if (prop.set || prop.writable) { //@ts-ignore t[p] = v didSet = true } break } o = Reflect.getPrototypeOf(o) } } if (!didSet) { // if nothing has that field, assign to the base //@ts-ignore base[p] = v } return true }, get(_, p) { if (p === 'parent') { return base.parent?.t } // cache get results so t.blah === t.blah // we only cache functions, so that getters aren't memoized // Of course, a getter that returns a function will be broken, // at least when accessed from outside the plugin, but that's // a pretty narrow caveat, and easily documented. if (getCache.has(p)) return getCache.get(p) for (const plug of ext) { if (p in plug) { //@ts-ignore const v = plug[p] // Functions need special handling so that they report // the correct toString and are called on the correct object // Otherwise attempting to access #private props will fail. if (typeof v === 'function') { if (getCache.has(v)) return getCache.get(v) const vv: Function = copyFunction(t, plug, v) getCache.set(p, vv) // aliases remain aliases getCache.set(v, vv) return vv } else { return v } } } }, }), ) // assign a reference to the extended Test for use in plugin at run-time Object.assign(base, { t }) // put the .t self-ref and plugin inspection on top of the stack const top = { t, get pluginLoaded() { return (plugin: (t: any, opts?: any) => T) => { return plugs.includes(plugin) } }, get plugins() { return [...plugs] }, } ext.unshift(top) //@ts-ignore const tst: string = base[Symbol.toStringTag] Object.defineProperty(t, Symbol.toStringTag, { value: tst, configurable: true, }) Object.defineProperty(top, Symbol.toStringTag, { value: tst, configurable: true, }) return t } const kPluginSet = Symbol('@tapjs/test construction plugin set') const kClass = Symbol('@tapjs/test construction class') /** * Option object used when extending the `Test` class via * {@link @tapjs/test!index.Test.applyPlugin} * * @internal */ export type PluginExtensionOption< E extends BuiltPlugins = BuiltPlugins, O extends TestOpts = TestOpts, > = { [kPluginSet]: TapPlugin[] [kClass]?: typeof Test } /** * interface defining the fully extended {@link @tapjs/test!index.Test} class. */ export interface Test< Ext extends BuiltPlugins = BuiltPlugins, Opts extends TestOpts = TestOpts, > extends TTest { /** * Explicitly mark the test as completed, outputting the TAP plan line if * needed. * * This is not required to be called if the test function returns a promise, * or if a plan is explicitly declared and eventually fulfilled. * * @group Test Lifecycle Management */ end(): this /** * Specify the number of Test Points expected by this test. * Outputs a TAP plan line. * * @group Test Lifecycle Management */ plan(n: number, comment?: string): void } /** * This is the class that is extended for the root {@link @tapjs/core!tap.TAP} * test, and used to instantiate test objects in its child tests. It extends * {@link @tapjs/core!test-base.TestBase}, and implements the union of return * values of all loaded plugins via a Proxy. */ export class Test< Ext extends BuiltPlugins = BuiltPlugins, Opts extends TestOpts = TestOpts, > extends TestBase implements TTest { #Class: typeof Test #pluginSet: TapPlugin[] /** * @param opts Test options for this instance * * @param __INTERNAL Extension option used by the subclasses created in * {@link @tapjs/test!index.Test#applyPlugin}. * * @internal */ constructor( opts: Opts, __INTERNAL: PluginExtensionOption = { [kPluginSet]: plugins() as TapPlugin[], [kClass]: Test, }, ) { super(opts) this.#Class = __INTERNAL[kClass] as typeof Test const pluginSet = __INTERNAL[kPluginSet] this.#pluginSet = pluginSet type T = Test & Ext // need to ignore this because it's a ctor that returns a value. /* c8 ignore start */ return applyPlugins(this, pluginSet) as T } /* c8 ignore stop */ /** * The string signature of the plugins built into this Test class */ get pluginSignature() { return signature } /** * Add a plugin at run-time. * * Creates a subclass of {@link @tapjs/test!index.Test} which has the * specified plugin, and which applies the plugin to all child tests it * creates. * * Typically, it's best to load plugins using configuration, set via the * `tap plugin ` command. * * However, in some cases, for example while developing plugins or if a * certain plugin is only needed in a small number of tests, it can be * useful to apply it after the fact. * * This is best used sparingly, as it may result in poor typescript * compilation performance, which can manifest in slower test start-up times * and lag loading autocomplete in editors. If you find yourself calling * applyPlugin often, consider whether it'd be better to just add the plugin * to the entire test suite, so that it can be built up front. * * @group Plugin Management */ applyPlugin( plugin: TapPlugin, ): Test & Ext & B { if (this.printedOutput) { throw new Error('Plugins must be applied prior to any test output') } if (this.#pluginSet.includes(plugin as TapPlugin)) { throw new Error('Plugin already applied') } type ExtExt = Ext & B type ExtOpts = Opts & O const p = plugin as TapPlugin const pluginSetExtended: TapPlugin[] = ( this.#pluginSet as TapPlugin[] ).concat([p]) const extended = this as unknown as Test & ExtExt class TestExtended extends Test { constructor( opts: ExtOpts, __INTERNAL: PluginExtensionOption = { [kPluginSet]: pluginSetExtended, [kClass]: TestExtended, }, ) { super(opts, __INTERNAL) } } extended.#pluginSet = pluginSetExtended extended.#Class = TestExtended Object.defineProperty(TestExtended, 'name', { value: 'Test', configurable: true, }) return applyPlugins(extended, pluginSetExtended) } // NB: this isn't ever actually called, because we add a pluginLoaded // method in the applyPlugins proxy, but it's here to establish the // type interface. /** * Return true if the specified plugin is loaded. Asserts that the * test object in question implements the return value of the plugin. * * @group Plugin Management */ pluginLoaded( plugin: (t: any, opts?: any) => T, ): this is TestBase & T { plugin return false } /** * Return the set of plugins loaded by this Test * * @group Plugin Management */ get plugins(): TapPlugin[] { return [] } /** * Create a child Test object and parse its output as a subtest * * @group Subtest Methods */ test( name: string, extra: Opts, cb: (t: Test & Ext) => any, ): PromiseWithSubtest & Ext> test( name: string, cb: (t: Test & Ext) => any, ): PromiseWithSubtest & Ext> test( extra: Opts, cb: (t: Test & Ext) => any, ): PromiseWithSubtest & Ext> test( cb: (t: Test & Ext) => any, ): PromiseWithSubtest & Ext> test( ...args: TestArgs & Ext, Opts> ): PromiseWithSubtest & Ext> { const extra = parseTestArgs & Ext, Opts>(...args) return this.sub(this.#Class, extra, this.test) as PromiseWithSubtest< Test & Ext > } /** * Create a subtest which is marked as `todo` * * @group Subtest Methods */ todo( name: string, extra: Opts, cb: (t: Test & Ext) => any, ): PromiseWithSubtest & Ext> todo( name: string, cb: (t: Test & Ext) => any, ): PromiseWithSubtest & Ext> todo( extra: Opts, cb: (t: Test & Ext) => any, ): PromiseWithSubtest & Ext> todo( cb: (t: Test & Ext) => any, ): PromiseWithSubtest & Ext> todo( ...args: TestArgs & Ext, Opts> ): PromiseWithSubtest & Ext> { const extra = parseTestArgs & Ext, Opts>(...args) extra.todo = true return this.sub(this.#Class, extra, this.todo) as PromiseWithSubtest< Test & Ext > } /** * Create a subtest which is marked as `skip` * * @group Subtest Methods */ skip( name: string, extra: Opts, cb: (t: Test & Ext) => any, ): PromiseWithSubtest & Ext> skip( name: string, cb: (t: Test & Ext) => any, ): PromiseWithSubtest & Ext> skip( extra: Opts, cb: (t: Test & Ext) => any, ): PromiseWithSubtest & Ext> skip( cb: (t: Test & Ext) => any, ): PromiseWithSubtest & Ext> skip( ...args: TestArgs & Ext, Opts> ): PromiseWithSubtest & Ext> { const extra = parseTestArgs & Ext, Opts>(...args) extra.skip = true return this.sub(this.#Class, extra, this.skip) as PromiseWithSubtest< Test & Ext > } }