From 64c5e97c136e8cdffcaf01b9fe870cd9fccc5217 Mon Sep 17 00:00:00 2001 From: "Xunnamius (Romulus)" Date: Thu, 2 Nov 2023 08:45:57 -0700 Subject: [PATCH] refactor: linting pass --- src/index.ts | 68 ++++---- test/integration-client-next.test.ts | 34 ++-- test/integration-external.test.ts | 5 +- test/integration-node.test.ts | 34 ++-- test/setup.ts | 229 +++++++++++++++------------ test/unit-index-imports.test.ts | 12 +- test/unit-index.test.ts | 4 +- 7 files changed, 208 insertions(+), 178 deletions(-) diff --git a/src/index.ts b/src/index.ts index 53093713..5e1c8c66 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,9 +5,9 @@ import { parse as parseUrl } from 'node:url'; import { parse as parseCookieHeader } from 'cookie'; import fetch, { Headers } from 'node-fetch'; -import type { IncomingMessage, Server, ServerResponse } from 'http'; import type { NextApiHandler } from 'next'; import type { Response as FetchReturnValue, RequestInit } from 'node-fetch'; +import type { IncomingMessage, Server, ServerResponse } from 'node:http'; import type { apiResolver as NextApiResolver } from 'next/dist/server/api-utils/node/api-resolver'; @@ -39,7 +39,7 @@ export type FetchReturnType = Promise< >; type TryImport = ((path: string) => ( - e: Error + error: Error ) => // @ts-ignore: this file might not exist in some versions of next Promise) & { importErrors: Error[]; @@ -47,8 +47,8 @@ Promise) & { // ? The result of this function is memoized by the caller, so this function // ? will only be invoked the first time this script is imported. -const tryImport = ((path: string) => (e: Error) => { - (tryImport.importErrors = tryImport.importErrors ?? []).push(e); +const tryImport = ((path: string) => (error: Error) => { + (tryImport.importErrors = tryImport.importErrors ?? []).push(error); /* istanbul ignore next */ if (typeof __webpack_require__ == 'function') { return process.env.NODE_ESM @@ -64,8 +64,8 @@ const tryImport = ((path: string) => (e: Error) => { const handleError = ( res: ServerResponse, - e: unknown, - deferredReject: ((e: unknown) => unknown) | null + error: unknown, + deferredReject: ((error: unknown) => unknown) | null ) => { // ? Prevent tests that crash the server from hanging if (!res.writableEnded) { @@ -78,8 +78,8 @@ const handleError = ( // ? functions (which is why `void` is used instead of `await` below). So // ? we'll have to get creative! How about: defer rejections manually? /* istanbul ignore else */ - if (deferredReject) deferredReject(e); - else throw e; + if (deferredReject) deferredReject(error); + else throw error; }; /** @@ -101,7 +101,7 @@ export type NtarhParameters = { * * **Note: all replacement `IncomingMessage.header` names must be lowercase.** */ - requestPatcher?: (req: IncomingMessage) => void; + requestPatcher?: (request: IncomingMessage) => void; /** * A function that receives a `ServerResponse` object. Use this functions to * edit the request before it's injected into the handler. @@ -113,7 +113,7 @@ export type NtarhParameters = { * should not be confused with query string parsing, which is handled * automatically. */ - paramsPatcher?: (params: Record) => void; + paramsPatcher?: (parameters: Record) => void; /** * `params` is passed directly to the handler and represent processed dynamic * routes. This should not be confused with query string parsing, which is @@ -141,7 +141,7 @@ export type NtarhParameters = { * `fetch`, which is the unfetch package's `fetch(...)` function but with the * first parameter omitted. */ - test: (params: { + test: (parameters: { fetch: (customInit?: RequestInit) => FetchReturnType; }) => Promise; }; @@ -162,7 +162,7 @@ export async function testApiHandler({ test }: NtarhParameters) { let server: Server | null = null; - let deferredReject: ((e?: unknown) => void) | null = null; + let deferredReject: ((error?: unknown) => void) | null = null; try { if (!apiResolver) { @@ -176,13 +176,15 @@ export async function testApiHandler({ .catch(tryImport('next/dist/next-server/server/api-utils.js')) // ? The following is for next@<9.0.6 >= 9.0.0: .catch(tryImport('next-server/dist/server/api-utils.js')) - .catch((e) => (tryImport.importErrors.push(e), { apiResolver: null }))); + .catch((error) => (tryImport.importErrors.push(error), { apiResolver: null }))); if (!apiResolver) { const importErrors = tryImport.importErrors .map( - (e) => - e.message.split(/(?<=')( imported)? from ('|\S)/)[0].split(`\nRequire`)[0] + (error) => + error.message + .split(/(?<=')( imported)? from ('|\S)/)[0] + .split(`\nRequire`)[0] ) .join('\n - '); @@ -199,24 +201,24 @@ export async function testApiHandler({ } } - server = createServer((req, res) => { + server = createServer((request, res) => { try { if (typeof apiResolver != 'function') { - throw new Error( + throw new TypeError( 'assertion failed unexpectedly: apiResolver was not a function' ); } if (url) { - req.url = url; + request.url = url; } - requestPatcher?.(req); + requestPatcher?.(request); responsePatcher?.(res); - const finalParams = { ...parseUrl(req.url || '', true).query, ...params }; + const finalParameters = { ...parseUrl(request.url || '', true).query, ...params }; - paramsPatcher?.(finalParams); + paramsPatcher?.(finalParameters); /** *? From Next.js internals: @@ -232,16 +234,16 @@ export async function testApiHandler({ ** ) */ void apiResolver( - req, + request, res, - finalParams, + finalParameters, handler, // eslint-disable-next-line @typescript-eslint/no-explicit-any undefined as any, !!rejectOnHandlerError - ).catch((e: unknown) => handleError(res, e, deferredReject)); - } catch (e) { - handleError(res, e, deferredReject); + ).catch((error: unknown) => handleError(res, error, deferredReject)); + } catch (error) { + handleError(res, error, deferredReject); } }); @@ -283,13 +285,13 @@ export async function testApiHandler({ res.cookies = [res.headers.raw()['set-cookie'] || []] .flat() .map((header) => - Object.entries(parseCookieHeader(header)).reduce( - (obj, [k, v]) => - Object.assign(obj, { - [String(k)]: v, - [String(k).toLowerCase()]: v - }), - {} + Object.fromEntries( + Object.entries(parseCookieHeader(header)).flatMap(([k, v]) => { + return [ + [String(k), v], + [String(k).toLowerCase(), v] + ]; + }) ) ); return res.cookies; diff --git a/test/integration-client-next.test.ts b/test/integration-client-next.test.ts index a700b851..3d0228d4 100644 --- a/test/integration-client-next.test.ts +++ b/test/integration-client-next.test.ts @@ -163,28 +163,30 @@ for (const [nextVersion, ...otherPkgVersions] of NEXT_VERSIONS_UNDER_TEST) { }; } - await withMockedFixture(async (ctx) => { - if (!ctx.testResult) throw new Error('must use node-import-test fixture'); + await withMockedFixture(async (context) => { + if (!context.testResult) throw new Error('must use node-import-test fixture'); if (esm) { debug('(expecting stderr to be "")'); debug('(expecting stdout to be "working\\nworking\\nworking")'); debug('(expecting exit code to be 0)'); - expect(ctx.testResult.stderr).toBeEmpty(); - expect(ctx.testResult.stdout).toBe('working\nworking\nworking'); - expect(ctx.testResult.code).toBe(0); + expect(context.testResult.stderr).toBeEmpty(); + expect(context.testResult.stdout).toBe('working\nworking\nworking'); + expect(context.testResult.code).toBe(0); } else { debug('(expecting stderr to contain jest test PASS confirmation)'); debug('(expecting stdout to contain "working")'); debug('(expecting exit code to be 0)'); - expect(stripAnsi(ctx.testResult.stderr)).toMatch( + expect(stripAnsi(context.testResult.stderr)).toMatch( /PASS.*?\s+src\/index\.test\.js/ ); - expect(ctx.testResult.stdout).toStrictEqual(expect.stringContaining('working')); - expect(ctx.testResult.code).toBe(0); + expect(context.testResult.stdout).toStrictEqual( + expect.stringContaining('working') + ); + expect(context.testResult.code).toBe(0); } }); @@ -244,32 +246,32 @@ it('fails fast (no jest timeout) when using incompatible Next.js version', async fixtureOptions.runWith = { binary: 'npx', args: ['jest'], - opts: { timeout: 10000 } + opts: { timeout: 10_000 } }; - await withMockedFixture(async (ctx) => { - if (!ctx.testResult) throw new Error('must use node-import-test fixture'); + await withMockedFixture(async (context) => { + if (!context.testResult) throw new Error('must use node-import-test fixture'); debug('(expecting stderr not to contain "Exceeded timeout")'); - expect(ctx.testResult.stderr).not.toStrictEqual( + expect(context.testResult.stderr).not.toStrictEqual( expect.stringContaining('Exceeded timeout') ); debug('(expecting stderr to contain "Failed import attempts:")'); - expect(ctx.testResult.stderr).toStrictEqual( + expect(context.testResult.stderr).toStrictEqual( expect.stringContaining('Failed import attempts:') ); debug('(expecting stderr to contain "3 failed, 3 total")'); - expect(ctx.testResult.stderr).toStrictEqual( + expect(context.testResult.stderr).toStrictEqual( expect.stringMatching(/^.*?Tests:.*?3 failed.*?,.*?3 total/m) ); debug('(expecting exit code to be non-zero)'); - expect(ctx.testResult.code).not.toBe(0); + expect(context.testResult.code).not.toBe(0); debug('(expecting no forced timeout: exit code must be a number)'); - expect(ctx.testResult.code).toBeNumber(); + expect(context.testResult.code).toBeNumber(); }); delete fixtureOptions.npmInstall; diff --git a/test/integration-external.test.ts b/test/integration-external.test.ts index 46c8c275..dd2997aa 100644 --- a/test/integration-external.test.ts +++ b/test/integration-external.test.ts @@ -54,7 +54,10 @@ it(`runs silent by default`, async () => { // eslint-disable-next-line jest/no-conditional-expect expect( // ? Remove outputs caused by Node's experimental warnings - stderr.replace(/^.*? (ExperimentalWarning:|--trace-warnings) .*?$/gm, '').trim() + // TODO: replace with suppression package + stderr + .replaceAll(/^.*? (ExperimentalWarning:|--trace-warnings) .*?$/gm, '') + .trim() ).toBeEmpty(); } diff --git a/test/integration-node.test.ts b/test/integration-node.test.ts index 01379fad..a5f479ff 100644 --- a/test/integration-node.test.ts +++ b/test/integration-node.test.ts @@ -54,9 +54,9 @@ const runTest = async ( }); })();`; - await withMockedFixture(async (ctx) => { - if (!ctx.testResult) throw new Error('must use node-import-test fixture'); - await testFixtureFn(ctx); + await withMockedFixture(async (context) => { + if (!context.testResult) throw new Error('must use node-import-test fixture'); + await testFixtureFn(context); }); delete fixtureOptions.initialFileContents[indexPath]; @@ -75,12 +75,12 @@ it('works as an ESM import', async () => { true, `res.status(status || 200).send({ works: 'working' });`, `console.log((await (await fetch()).json()).works)`, - async (ctx) => { + async (context) => { debug('(expecting stdout to be "working")'); debug('(expecting exit code to be 0)'); - expect(ctx.testResult?.stdout).toBe('working'); - expect(ctx.testResult?.code).toBe(0); + expect(context.testResult?.stdout).toBe('working'); + expect(context.testResult?.code).toBe(0); } ); }); @@ -91,12 +91,12 @@ it('works as a CJS require(...)', async () => { false, `res.status(status || 200).send({ works: 'working' });`, `console.log((await (await fetch()).json()).works)`, - async (ctx) => { + async (context) => { debug('(expecting stdout to be "working")'); debug('(expecting exit code to be 0)'); - expect(ctx.testResult?.stdout).toBe('working'); - expect(ctx.testResult?.code).toBe(0); + expect(context.testResult?.stdout).toBe('working'); + expect(context.testResult?.code).toBe(0); } ); }); @@ -107,16 +107,16 @@ it('does not hang (500ms limit) on exception in handler function', async () => { false, `throw new Error('BadBadNotGood');`, `console.log(await (await fetch()).text())`, - async (ctx) => { + async (context) => { debug('(expecting stdout to be "Internal Server Error")'); debug('(expecting stderr to contain "BadBadNotGood")'); debug('(expecting exit code to be non-zero)'); - expect(ctx.testResult?.stdout).toBe('Internal Server Error'); - expect(ctx.testResult?.stderr).toStrictEqual( + expect(context.testResult?.stdout).toBe('Internal Server Error'); + expect(context.testResult?.stderr).toStrictEqual( expect.stringContaining('Error: BadBadNotGood') ); - expect(ctx.testResult?.code).toBe(0); + expect(context.testResult?.code).toBe(0); } ); }, 500); @@ -127,18 +127,18 @@ it('does not hang (500ms limit) on exception in test function', async () => { false, `res.status(status || 200).send({ works: 'working' });`, `{ throw new Error('BadBadNotGood'); }`, - async (ctx) => { + async (context) => { debug('(expecting exit code to be non-zero)'); debug('(expecting stdout to be "")'); debug('(expecting stderr to contain "BadBadNotGood")'); - expect(ctx.testResult?.code).toBe( + expect(context.testResult?.code).toBe( // ? node@<15 does not die on unhandled promise rejections Number(process.versions.node.split('.')[0]) < 15 ? 0 : 1 ); - expect(ctx.testResult?.stdout).toBeEmpty(); - expect(ctx.testResult?.stderr).toStrictEqual( + expect(context.testResult?.stdout).toBeEmpty(); + expect(context.testResult?.stderr).toStrictEqual( expect.stringContaining('Error: BadBadNotGood') ); } diff --git a/test/setup.ts b/test/setup.ts index fedc8830..b0c4c760 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -1,3 +1,5 @@ +/* eslint-disable unicorn/no-object-as-default-parameter */ +// TODO: remove the previous line ^^^ import { promises as fs } from 'node:fs'; import { tmpdir } from 'node:os'; import { resolve } from 'node:path'; @@ -12,7 +14,7 @@ import uniqueFilename from 'unique-filename'; import 'jest-extended'; import 'jest-extended/all'; -import type { AnyFunction, AnyVoid } from '@ergodark/types'; +import type { AnyFunction, AnyVoid } from '@xunnamius/types'; import type { Debugger } from 'debug'; import type { ExecaReturnValue } from 'execa'; import type { SimpleGit } from 'simple-git'; @@ -58,13 +60,13 @@ export async function withMockedArgv( options: MockArgvOptions = { replace: false } ) { // ? Take care to preserve the original argv array reference in memory - const prevArgv = process.argv.splice(options?.replace ? 0 : 2, process.argv.length); + const previousArgv = process.argv.splice(options?.replace ? 0 : 2, process.argv.length); process.argv.push(...newArgv); await fn(); process.argv.splice(options?.replace ? 0 : 2, process.argv.length); - process.argv.push(...prevArgv); + process.argv.push(...previousArgv); } // TODO: XXX: make this into a separate (mock-argv) package (along w/ the above) @@ -90,7 +92,7 @@ export async function withMockedEnv( newEnv: Record, options: MockEnvOptions = { replace: true } ) { - const prevEnv = { ...process.env }; + const previousEnv = { ...process.env }; const clearEnv = () => Object.getOwnPropertyNames(process.env).forEach((prop) => delete process.env[prop]); @@ -101,25 +103,29 @@ export async function withMockedEnv( await fn(); clearEnv(); - Object.assign(process.env, prevEnv); + Object.assign(process.env, previousEnv); } // TODO: XXX: make this into a separate (mock-env) package (along w/ the above) export function mockEnvFactory( + // eslint-disable-next-line unicorn/no-keyword-prefix newEnv: Record, options: MockEnvOptions = { replace: true } ) { + // eslint-disable-next-line unicorn/no-keyword-prefix const factoryNewEnv = newEnv; const factoryOptions = options; return ( fn: () => AnyVoid, + // eslint-disable-next-line unicorn/no-keyword-prefix newEnv?: Record, options?: MockEnvOptions ) => { return withMockedEnv( fn, - { ...factoryNewEnv, ...(newEnv || {}) }, + // eslint-disable-next-line unicorn/no-keyword-prefix + { ...factoryNewEnv, ...newEnv }, options || factoryOptions ); }; @@ -194,13 +200,13 @@ export async function withMockedExit( // TODO: XXX: make this into a separate package (along with the above) export function protectedImportFactory(path: string) { - return async (params?: { expectedExitCode?: number }) => { + return async (parameters?: { expectedExitCode?: number }) => { let pkg: unknown = undefined; await withMockedExit(async ({ exitSpy }) => { pkg = await isolatedImport({ path }); - if (expect && params?.expectedExitCode) - expect(exitSpy).toBeCalledWith(params.expectedExitCode); + if (expect && parameters?.expectedExitCode) + expect(exitSpy).toBeCalledWith(parameters.expectedExitCode); else if (!expect) debug('WARNING: "expect" object not found, so exit check was skipped'); }); @@ -347,11 +353,13 @@ export interface GitProvider { // TODO: XXX: make this into a separate (mock-fixture) package (along w/ below) // eslint-disable-next-line @typescript-eslint/ban-types -export type FixtureAction = (ctx: Context) => Promise; +export type FixtureAction = ( + context: Context +) => Promise; // TODO: XXX: make this into a separate (mock-fixture) package (along w/ below) export type ReturnsString = ( - ctx: Context + context: Context ) => Promise | string; // TODO: XXX: make this into a separate (mock-fixture) package (along w/ below) @@ -366,18 +374,20 @@ export interface MockFixture { export function rootFixture(): MockFixture { return { name: 'root', // ? If first isn't named root, root used automatically - description: (ctx) => + description: (context) => `creating a unique root directory${ - ctx.options.performCleanup ? ' (will be deleted after all tests complete)' : '' + context.options.performCleanup + ? ' (will be deleted after all tests complete)' + : '' }`, - setup: async (ctx) => { - ctx.root = uniqueFilename(tmpdir(), ctx.testIdentifier); + setup: async (context) => { + context.root = uniqueFilename(tmpdir(), context.testIdentifier); - await run('mkdir', ['-p', ctx.root], { reject: true }); - await run('mkdir', ['-p', 'src'], { cwd: ctx.root, reject: true }); + await run('mkdir', ['-p', context.root], { reject: true }); + await run('mkdir', ['-p', 'src'], { cwd: context.root, reject: true }); }, - teardown: async (ctx) => - ctx.options.performCleanup && run('rm', ['-rf', ctx.root], { reject: true }) + teardown: async (context) => + context.options.performCleanup && run('rm', ['-rf', context.root], { reject: true }) }; } @@ -386,19 +396,19 @@ export function dummyNpmPackageFixture(): MockFixture { return { name: 'dummy-npm-package', description: 'creating package.json file and node_modules subdirectory', - setup: async (ctx) => { + setup: async (context) => { await Promise.all([ writeFile( - `${ctx.root}/package.json`, - (ctx.fileContents['package.json'] = - ctx.fileContents['package.json'] || '{"name":"dummy-pkg"}') + `${context.root}/package.json`, + (context.fileContents['package.json'] = + context.fileContents['package.json'] || '{"name":"dummy-pkg"}') ), - run('mkdir', ['-p', 'node_modules'], { cwd: ctx.root, reject: true }) + run('mkdir', ['-p', 'node_modules'], { cwd: context.root, reject: true }) ]); if (pkgName.includes('/')) { await run('mkdir', ['-p', pkgName.split('/')[0]], { - cwd: `${ctx.root}/node_modules`, + cwd: `${context.root}/node_modules`, reject: true }); } @@ -412,9 +422,9 @@ export function npmLinkSelfFixture(): MockFixture { name: 'npm-link-self', description: 'soft-linking project repo into node_modules to emulate package installation', - setup: async (ctx) => { + setup: async (context) => { await run('ln', ['-s', resolve(`${__dirname}/..`), pkgName], { - cwd: `${ctx.root}/node_modules`, + cwd: `${context.root}/node_modules`, reject: true }); } @@ -427,17 +437,17 @@ export function npmCopySelfFixture(): MockFixture { name: 'npm-copy-self', description: 'copying package.json#files into node_modules to emulate package installation', - setup: async (ctx) => { + setup: async (context) => { const root = resolve(`${__dirname}/..`); const { files: patterns } = await import('package'); const files = patterns.flatMap((p) => glob.sync(p, { cwd: root, root })); - const dest = `${ctx.root}/node_modules/${pkgName}`; + const dest = `${context.root}/node_modules/${pkgName}`; const destPkgJson = `${dest}/package.json`; - ctx.debug(`cp destination: ${dest}`); - ctx.debug(`cp sources (cwd: ${root}): %O`, files); + context.debug(`cp destination: ${dest}`); + context.debug(`cp sources (cwd: ${root}): %O`, files); await run('mkdir', ['-p', dest], { reject: true }); await run('cp', ['-r', ...files, dest], { cwd: root, reject: true }); @@ -453,11 +463,13 @@ export function npmCopySelfFixture(): MockFixture { peerDependencies: _, devDependencies: __, ...dummyPkgJson - } = JSON.parse(await readFile(destPkgJson, 'utf-8')); + } = JSON.parse(await readFile(destPkgJson, 'utf8')); - const installTargets = [ctx.options.npmInstall] + const installTargets = [context.options.npmInstall] .flat() .filter((r): r is string => Boolean(r)) + // TODO: fix/merge this + // eslint-disable-next-line unicorn/no-array-reduce .reduce>((obj, pkgStr) => { const pkg = pkgStr.split('@'); return { ...obj, [pkg[0]]: pkg[1] || 'latest' }; @@ -475,21 +487,21 @@ export function npmCopySelfFixture(): MockFixture { }); await run('mv', ['node_modules', 'node_modules_old'], { - cwd: ctx.root, + cwd: context.root, reject: true }); await run('mv', [`node_modules_old/${pkgName}/node_modules`, '.'], { - cwd: ctx.root, + cwd: context.root, reject: true }); await run('mv', [`node_modules_old/${pkgName}`, 'node_modules'], { - cwd: ctx.root, + cwd: context.root, reject: true }); - await run('rm', ['-rf', 'node_modules_old'], { cwd: ctx.root, reject: true }); + await run('rm', ['-rf', 'node_modules_old'], { cwd: context.root, reject: true }); } }; } @@ -499,42 +511,52 @@ export function webpackTestFixture(): MockFixture { return { name: 'webpack-test', description: 'setting up webpack jest integration test', - setup: async (ctx) => { - if (typeof ctx.options.webpackVersion != 'string') { - throw new Error('invalid or missing options.webpackVersion, expected string'); + setup: async (context) => { + if (typeof context.options.webpackVersion != 'string') { + throw new TypeError('invalid or missing options.webpackVersion, expected string'); } - const indexPath = Object.keys(ctx.fileContents).find((path) => + const indexPath = Object.keys(context.fileContents).find((path) => /^src\/index\.(((c|m)?js)|ts)x?$/.test(path) ); if (!indexPath) throw new Error('could not find initial contents for src/index file'); - if (!ctx.fileContents['webpack.config.js']) + if (!context.fileContents['webpack.config.js']) throw new Error('could not find initial contents for webpack.config.js file'); await Promise.all([ - writeFile(`${ctx.root}/${indexPath}`, ctx.fileContents[indexPath]), - writeFile(`${ctx.root}/webpack.config.js`, ctx.fileContents['webpack.config.js']) + writeFile(`${context.root}/${indexPath}`, context.fileContents[indexPath]), + writeFile( + `${context.root}/webpack.config.js`, + context.fileContents['webpack.config.js'] + ) ]); - ctx.treeOutput = await getTreeOutput(ctx); + context.treeOutput = await getTreeOutput(context); await run( 'npm', - ['install', '--no-save', `webpack@${ctx.options.webpackVersion}`, 'webpack-cli'], + [ + 'install', + '--no-save', + `webpack@${context.options.webpackVersion}`, + 'webpack-cli' + ], { - cwd: ctx.root, + cwd: context.root, reject: true } ); - await run('npx', ['webpack'], { cwd: ctx.root, reject: true }); + await run('npx', ['webpack'], { cwd: context.root, reject: true }); - const { code, stdout, stderr } = await run('node', [`${ctx.root}/dist/index.js`]); + const { code, stdout, stderr } = await run('node', [ + `${context.root}/dist/index.js` + ]); - ctx.testResult = { + context.testResult = { code, stdout, stderr @@ -543,8 +565,8 @@ export function webpackTestFixture(): MockFixture { }; } -async function getTreeOutput(ctx: FixtureContext) { - return (await execa('tree', ['-a', '-L', '2'], { cwd: ctx.root })).stdout; +async function getTreeOutput(context: FixtureContext) { + return (await execa('tree', ['-a', '-L', '2'], { cwd: context.root })).stdout; } // TODO: XXX: make this into a separate (mock-fixture) package (along w/ below) @@ -552,30 +574,30 @@ export function nodeImportTestFixture(): MockFixture { return { name: 'node-import-test', description: 'setting up node import jest integration test', - setup: async (ctx) => { - const indexPath = Object.keys(ctx.fileContents).find((path) => + setup: async (context) => { + const indexPath = Object.keys(context.fileContents).find((path) => /^src\/index(\.test)?\.(((c|m)?js)|ts)x?$/.test(path) ); if (!indexPath) throw new Error('could not find initial contents for src/index test file'); - await writeFile(`${ctx.root}/${indexPath}`, ctx.fileContents[indexPath]); + await writeFile(`${context.root}/${indexPath}`, context.fileContents[indexPath]); // TODO: also test all current/active/maintenance versions of node too // TODO: and enable that functionality - const bin = ctx.options.runWith?.binary || 'node'; - const args = ctx.options.runWith?.args || ['--experimental-json-modules']; - const opts = ctx.options.runWith?.opts || {}; + const bin = context.options.runWith?.binary || 'node'; + const args = context.options.runWith?.args || ['--experimental-json-modules']; + const options = context.options.runWith?.opts || {}; - ctx.treeOutput = await getTreeOutput(ctx); + context.treeOutput = await getTreeOutput(context); const { code, stdout, stderr } = await run(bin, [...args, indexPath], { - cwd: ctx.root, - ...opts + cwd: context.root, + ...options }); - ctx.testResult = { + context.testResult = { code, stdout, stderr @@ -589,16 +611,16 @@ export function gitRepositoryFixture(): MockFixture { return { name: 'git-repository', description: 'configuring fixture root to be a git repository', - setup: async (ctx) => { - if (ctx.options.setupGit && typeof ctx.options.setupGit != 'function') { + setup: async (context) => { + if (context.options.setupGit && typeof context.options.setupGit != 'function') { throw new Error('invalid or missing options.setupGit, expected function'); } - ctx.git = gitFactory({ baseDir: ctx.root }); + context.git = gitFactory({ baseDir: context.root }); - await (ctx.options.setupGit - ? ctx.options.setupGit(ctx.git) - : ctx.git + await (context.options.setupGit + ? context.options.setupGit(context.git) + : context.git .init() .addConfig('user.name', 'fake-user') .addConfig('user.email', 'fake@email')); @@ -611,14 +633,14 @@ export function dummyDirectoriesFixture(): MockFixture { return { name: 'dummy-directories', description: 'creating dummy directories under fixture root', - setup: async (ctx) => { - if (!Array.isArray(ctx.options.directoryPaths)) { - throw new Error('invalid or missing options.directoryPaths, expected array'); + setup: async (context) => { + if (!Array.isArray(context.options.directoryPaths)) { + throw new TypeError('invalid or missing options.directoryPaths, expected array'); } await Promise.all( - ctx.options.directoryPaths.map((path) => - run('mkdir', ['-p', path], { cwd: ctx.root, reject: true }) + context.options.directoryPaths.map((path) => + run('mkdir', ['-p', path], { cwd: context.root, reject: true }) ) ); } @@ -630,16 +652,16 @@ export function dummyFilesFixture(): MockFixture { return { name: 'dummy-files', description: 'creating dummy files under fixture root', - setup: async (ctx) => { + setup: async (context) => { await Promise.all( - Object.entries(ctx.fileContents).map(async ([path, contents]) => { - const fullPath = `${ctx.root}/${path}`; + Object.entries(context.fileContents).map(async ([path, contents]) => { + const fullPath = `${context.root}/${path}`; await accessFile(fullPath).then( () => debug(`skipped creating dummy file: file already exists at ${path}`), async () => { debug(`creating dummy file "${path}" with contents:`); debug.extend('contents >')(contents); - await writeFile(fullPath, (ctx.fileContents[path] = contents)); + await writeFile(fullPath, (context.fileContents[path] = contents)); } ); }) @@ -655,11 +677,11 @@ export function describeRootFixture(): MockFixture { return { name: 'describe-root', description: 'outputting debug information about environment', - setup: async (ctx) => { - ctx.debug('test identifier: %O', ctx.testIdentifier); - ctx.debug('root: %O', ctx.root); - ctx.debug(ctx.treeOutput || (await getTreeOutput(ctx))); - ctx.debug('per-file contents: %O', ctx.fileContents); + setup: async (context) => { + context.debug('test identifier: %O', context.testIdentifier); + context.debug('root: %O', context.root); + context.debug(context.treeOutput || (await getTreeOutput(context))); + context.debug('per-file contents: %O', context.fileContents); } }; } @@ -696,7 +718,7 @@ export async function withMockedFixture< ...options } as CustomizedFixtureOptions & { use: CustomizedMockFixture[] }; - const ctx = { + const context = { root: '', testIdentifier, debug, @@ -706,15 +728,15 @@ export async function withMockedFixture< } as CustomizedFixtureContext & { using: CustomizedMockFixture[] }; if (finalOptions.use) { - if (finalOptions.use?.[0]?.name != 'root') ctx.using.push(rootFixture()); - ctx.using = [...ctx.using, ...finalOptions.use]; + if (finalOptions.use?.[0]?.name != 'root') context.using.push(rootFixture()); + context.using = [...context.using, ...finalOptions.use]; // ? `describe-root` fixture doesn't have to be the last one, but a fixture // ? with that name must be included at least once - if (!finalOptions.use.find((f) => f.name == 'describe-root')) - ctx.using.push(describeRootFixture()); - } else ctx.using = [rootFixture(), describeRootFixture()]; + if (!finalOptions.use.some((f) => f.name == 'describe-root')) + context.using.push(describeRootFixture()); + } else context.using = [rootFixture(), describeRootFixture()]; - ctx.using.push({ + context.using.push({ name: testSymbol, description: '', setup: fn @@ -727,19 +749,20 @@ export async function withMockedFixture< const toString = async ( p: CustomizedMockFixture['name'] | CustomizedMockFixture['description'] // TODO: replace with toss - ) => (typeof p == 'function' ? p(ctx) : typeof p == 'string' ? p : ':impossible:'); + ) => + typeof p == 'function' ? p(context) : typeof p == 'string' ? p : ':impossible:'; const name = await toString(fixture.name.toString()); const desc = await toString(fixture.description); const dbg = debug.extend(error ? `${name}:` : name); - ctx.debug = dbg; + context.debug = dbg; dbg(desc); }; /*eslint-disable no-await-in-loop */ try { - for (const mockFixture of ctx.using) { + for (const mockFixture of context.using) { if (mockFixture.name == testSymbol) { - ctx.debug = debug; + context.debug = debug; debug('executing test callback'); } else { await setupDebugger(mockFixture); @@ -747,28 +770,28 @@ export async function withMockedFixture< } mockFixture.setup - ? await mockFixture.setup(ctx) - : ctx.debug('(warning: mock fixture has no setup function)'); + ? await mockFixture.setup(context) + : context.debug('(warning: mock fixture has no setup function)'); if (mockFixture.name == 'describe-root') ranDescribe = true; } - } catch (e) { - ctx.debug.extend('')('exception occurred: %O', e); - throw e; + } catch (error) { + context.debug.extend('')('exception occurred: %O', error); + throw error; } finally { if (!ranDescribe) { const fixture = describeRootFixture(); await setupDebugger(fixture, true); - await fixture.setup?.(ctx); + await fixture.setup?.(context); } - ctx.debug = debug.extend(''); + context.debug = debug.extend(''); for (const cfn of cleanupFunctions.reverse()) { - await cfn(ctx).catch((e) => - ctx.debug( + await cfn(context).catch((error) => + context.debug( `ignored exception in teardown function: ${ - e?.message || e.toString() || '' + error?.message || error.toString() || '' }` ) ); diff --git a/test/unit-index-imports.test.ts b/test/unit-index-imports.test.ts index 9daeb12b..95b974f8 100644 --- a/test/unit-index-imports.test.ts +++ b/test/unit-index-imports.test.ts @@ -223,8 +223,8 @@ describe('::testApiHandler', () => { const previousResolverPaths = mockResolverPaths.slice(0, currentIndex); const nextResolverPaths = mockResolverPaths.slice(currentIndex + 1); - for (const prevResolverPath of previousResolverPaths) { - mockResolversMetadata[prevResolverPath].shouldFail = true; + for (const previousResolverPath of previousResolverPaths) { + mockResolversMetadata[previousResolverPath].shouldFail = true; } // eslint-disable-next-line no-await-in-loop @@ -237,9 +237,9 @@ describe('::testApiHandler', () => { }) ).toResolve(); - for (const prevResolverPath of previousResolverPaths) { - const context = jestExpectationContextFactory(prevResolverPath); - expect(context(mockResolversMetadata[prevResolverPath].called)).toBe( + for (const previousResolverPath of previousResolverPaths) { + const context = jestExpectationContextFactory(previousResolverPath); + expect(context(mockResolversMetadata[previousResolverPath].called)).toBe( context(false) ); } @@ -267,7 +267,7 @@ describe('::testApiHandler', () => { // ? Should be in reverse alphabetical order const expectedFailureLetters = Array.from({ length: mockResolverPaths.length }) - .map((_, index) => String.fromCharCode(65 + index)) + .map((_, index) => String.fromCodePoint(65 + index)) .reverse(); // eslint-disable-next-line no-await-in-loop diff --git a/test/unit-index.test.ts b/test/unit-index.test.ts index 7c70bbdb..cdb44655 100644 --- a/test/unit-index.test.ts +++ b/test/unit-index.test.ts @@ -287,7 +287,7 @@ describe('::testApiHandler', () => { expect.hasAssertions(); await testApiHandler({ - paramsPatcher: (params) => (params.a = '1'), + paramsPatcher: (parameters) => (parameters.a = '1'), requestPatcher: (req) => (req.url = '/api/handler?b=2&c=3'), handler: async (req, res) => { res.send({}); @@ -302,7 +302,7 @@ describe('::testApiHandler', () => { await testApiHandler({ params: { d: 'z', e: 'f' }, - paramsPatcher: (params) => (params.a = '1'), + paramsPatcher: (parameters) => (parameters.a = '1'), requestPatcher: (req) => (req.url = '/api/handler?b=2&c=3'), handler: async (req, res) => { res.send({});