Skip to content

Commit

Permalink
refactor: linting pass
Browse files Browse the repository at this point in the history
  • Loading branch information
Xunnamius committed Nov 2, 2023
1 parent db0223e commit 64c5e97
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 178 deletions.
68 changes: 35 additions & 33 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -39,16 +39,16 @@ export type FetchReturnType<NextResponseJsonType> = Promise<
>;

type TryImport = ((path: string) => (
e: Error
error: Error
) => // @ts-ignore: this file might not exist in some versions of next
Promise<typeof import('next/dist/server/api-utils/node.js')>) & {
importErrors: Error[];
};

// ? 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
Expand All @@ -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) {
Expand All @@ -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;
};

/**
Expand All @@ -101,7 +101,7 @@ export type NtarhParameters<NextResponseJsonType = unknown> = {
*
* **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.
Expand All @@ -113,7 +113,7 @@ export type NtarhParameters<NextResponseJsonType = unknown> = {
* should not be confused with query string parsing, which is handled
* automatically.
*/
paramsPatcher?: (params: Record<string, unknown>) => void;
paramsPatcher?: (parameters: Record<string, unknown>) => void;
/**
* `params` is passed directly to the handler and represent processed dynamic
* routes. This should not be confused with query string parsing, which is
Expand Down Expand Up @@ -141,7 +141,7 @@ export type NtarhParameters<NextResponseJsonType = unknown> = {
* `fetch`, which is the unfetch package's `fetch(...)` function but with the
* first parameter omitted.
*/
test: (params: {
test: (parameters: {
fetch: (customInit?: RequestInit) => FetchReturnType<NextResponseJsonType>;
}) => Promise<void>;
};
Expand All @@ -162,7 +162,7 @@ export async function testApiHandler<NextResponseJsonType = any>({
test
}: NtarhParameters<NextResponseJsonType>) {
let server: Server | null = null;
let deferredReject: ((e?: unknown) => void) | null = null;
let deferredReject: ((error?: unknown) => void) | null = null;

try {
if (!apiResolver) {
Expand All @@ -176,13 +176,15 @@ export async function testApiHandler<NextResponseJsonType = any>({
.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 - ');

Expand All @@ -199,24 +201,24 @@ export async function testApiHandler<NextResponseJsonType = any>({
}
}

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:
Expand All @@ -232,16 +234,16 @@ export async function testApiHandler<NextResponseJsonType = any>({
** )
*/
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);
}
});

Expand Down Expand Up @@ -283,13 +285,13 @@ export async function testApiHandler<NextResponseJsonType = any>({
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;
Expand Down
34 changes: 18 additions & 16 deletions test/integration-client-next.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
});

Expand Down Expand Up @@ -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;
Expand Down
5 changes: 4 additions & 1 deletion test/integration-external.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down
34 changes: 17 additions & 17 deletions test/integration-node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand All @@ -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);
}
);
});
Expand All @@ -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);
}
);
});
Expand All @@ -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);
Expand All @@ -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')
);
}
Expand Down
Loading

0 comments on commit 64c5e97

Please sign in to comment.