Skip to content

Commit

Permalink
build: prepare compatibility layer for next@15
Browse files Browse the repository at this point in the history
  • Loading branch information
Xunnamius committed Oct 17, 2024
1 parent c2f3de3 commit a73f21e
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 46 deletions.
15 changes: 14 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,17 @@ const apiResolver = findNextjsInternalResolver<

const AppRouteRouteModule = findNextjsInternalResolver<
// * Copied from the first line in the possibleLocations array below
// TODO:
// @ts-expect-error: remove this error expectation when next@15 drops
typeof import('next/dist/server/future/route-modules/app-route/module').AppRouteRouteModule
>('AppRouteRouteModule', [
// TODO: make this the last element in the array once next@15 drops
// ? The following is for next@>=14.0.4:
'next/dist/server/future/route-modules/app-route/module.js'
'next/dist/server/future/route-modules/app-route/module.js',

// TODO: make this the first element in the array once next@15 drops
// ? The following is for next@>=15.0.0-rc.1:
'next/dist/server/route-modules/app-route/module.js'
]);

// * ^^^ FIND NEXTJS INTERNAL RESOLVERS ^^^ * \\
Expand Down Expand Up @@ -155,9 +162,15 @@ export interface NtarhInitAppRouter<NextResponseJsonType = unknown>
* for details.
*/
appHandler: Omit<
// TODO:
// @ts-expect-error: remove this error expectation when next@15 drops
import('next/dist/server/future/route-modules/app-route/module').AppRouteUserlandModule,
// TODO:
// @ts-expect-error: remove this error expectation when next@15 drops
keyof import('next/dist/server/future/route-modules/app-route/module').AppRouteHandlers
> & {
// TODO:
// @ts-expect-error: remove this error expectation when next@15 drops
[key in keyof import('next/dist/server/future/route-modules/app-route/module').AppRouteHandlers]?: (
req: NextRequest,
context?: any
Expand Down
47 changes: 31 additions & 16 deletions test/integration-client-next.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,31 @@ import {

const TEST_IDENTIFIER = 'integration-client-next';

/* prettier-ignore */
const NEXT_VERSIONS_UNDER_TEST: [next: `next@${string}`, routerType: 'app' | 'pages' | 'both'][] = [
const NEXT_VERSIONS_UNDER_TEST: [
next: `next@${string}`,
routerType: 'app' | 'pages' | 'both',
additionalConfig?: { extraInstalls?: string[] }
][] = [
// * [next@version, routerType]
['next@9.0.0', 'pages'], // ? Earliest compat release
['next@^9', 'pages'], // ? Latest version 9 release
['next@10.1.x', 'pages'], // ? See issue #184
['next@^10', 'pages'], // ? Latest version 10 release
['next@11.0.x', 'pages'], // ? See issue #295
['next@^11', 'pages'], // ? Latest version 11 release
['next@12.0.x', 'pages'], // ? See issue #487
['next@13.5.3', 'pages'], // ? See issue #887
['next@14.0.4', 'both'], // ? Ntarh guarantees App Router support here on
['next@14.2.11', 'both'], // ? See issue #1076
['next@latest', 'both'] // ! Latest release (must always be here)
['next@9.0.0', 'pages'], // ? Earliest compat release
['next@^9', 'pages'], // ? Latest version 9 release
['next@10.1.x', 'pages'], // ? See issue #184
['next@^10', 'pages'], // ? Latest version 10 release
['next@11.0.x', 'pages'], // ? See issue #295
['next@^11', 'pages'], // ? Latest version 11 release
['next@12.0.x', 'pages'], // ? See issue #487
['next@13.5.3', 'pages'], // ? See issue #887
['next@14.0.4', 'both'], // ? Ntarh guarantees App Router support here on
['next@14.2.11', 'both'], // ? See issue #1076
// TODO: update/remove next entry when next@15 drops
[
'next@15.0.0-rc.1', // ? Added preemptively
'app',
{
extraInstalls: ['react@19.0.0-rc-cd22717c-20241013']
}
],
['next@latest', 'both'] // ! Latest release (must always be here and last)
];

const pkgMainPaths = Object.values(pkgExports)
Expand Down Expand Up @@ -64,7 +75,11 @@ beforeAll(async () => {
);
});

for (const [nextVersion, routerType_] of NEXT_VERSIONS_UNDER_TEST) {
for (const [
nextVersion,
routerType_,
{ extraInstalls = [] } = {}
] of NEXT_VERSIONS_UNDER_TEST) {
const routerTypes: ['app'] | ['pages'] | ['app', 'pages'] =
routerType_ === 'both' ? ['app', 'pages'] : [routerType_];

Expand Down Expand Up @@ -151,7 +166,7 @@ ${commonSrc}
});
})();`
},
npmInstall: [nextVersion]
npmInstall: [nextVersion, ...extraInstalls]
}
: {
initialFileContents: {
Expand Down Expand Up @@ -189,7 +204,7 @@ it('does what I want 3', async () => {
});
});`
},
npmInstall: ['jest', nextVersion],
npmInstall: ['jest', nextVersion, ...extraInstalls],
runWith: {
binary: 'npx',
args: ['jest']
Expand Down
32 changes: 29 additions & 3 deletions test/unit-index-imports.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,17 @@ import type { IncomingMessage, ServerResponse } from 'node:http';
// * fail letter

// ? The currently correct import path for the apiResolver function.
// * AA (-1)
// * AA (-2)
// TODO: becomes BB (-1) once next@15 drops
const actualAppRouteRouteModulePath =
'next/dist/server/future/route-modules/app-route/module.js';
// ? Defunct import paths listed by discovery date in ascending order. That is:
// ? previous actualAppRouteRouteModulePaths should be appended to the end of
// ? this array.
const altAppRouteRouteModulePaths: string[] = [
// * None so far!
// * BB (-1)
// TODO: becomes AA (-2) once next@15 drops
'next/dist/server/route-modules/app-route/module.js'
];

// ? The currently correct import path for the apiResolver function.
Expand All @@ -52,6 +55,7 @@ const altApiResolverPaths: string[] = [
// ! Only the TOP MOCKS should be { virtual: false }. The others must be
// ! { virtual: true }

// TODO: swap with BB (-1) once next@15 drops
jest.mock('next/dist/server/future/route-modules/app-route/module.js', () => {
return new Proxy(
{},
Expand Down Expand Up @@ -104,7 +108,29 @@ jest.mock('next/dist/server/api-utils/node/api-resolver.js', () => {

// * vvv REMAINING AppRouteRouteModule MOCKS vvv * \\

// * None so far!
jest.mock(
// TODO: swap with AA (-2) once next@15 drops
'next/dist/server/api-utils/node.js',
() => {
return new Proxy(
{},
{
get: function (_, key) {
if (mockApiResolverPaths && mockResolversMetadata) {
const meta = mockResolversMetadata[mockApiResolverPaths.at(-1)!];

if (meta.shouldFail) {
throw new Error(`fake import failure BB`);
} else if (key === 'AppRouteRouteModule') {
return getMockAppRouteRouteModule(meta);
}
} else throw new Error('proxy BB invoked too early');
}
}
);
},
{ virtual: true }
);

// * ^^^ REMAINING AppRouteRouteModule MOCKS ^^^ * \\

Expand Down
56 changes: 30 additions & 26 deletions test/unit-index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,20 +80,21 @@ describe('::testApiHandler', () => {

it('uses cached global fetch as ::test({ fetch }) param', async () => {
expect.hasAssertions();
expect(
// @ts-expect-error: a hidden property
fetch.__nextPatched
).toBeUndefined();
// expect(
// // @ts-expect-error: a hidden property
// fetch.__nextPatched
// ).toBeUndefined();

let ran = false;

await testApiHandler({
rejectOnHandlerError: true,
appHandler: {
GET() {
expect(
// @ts-expect-error: a hidden property
fetch.__nextPatched
).toBeTrue();
// expect(
// // @ts-expect-error: a hidden property
// fetch.__nextPatched
// ).toBeTrue();

ran = true;
return Response.json({ hello: 'world' });
Expand Down Expand Up @@ -481,7 +482,7 @@ describe('::testApiHandler', () => {
['c', '3']
]);

expect(params).toStrictEqual({ a: '1' });
await expect(params).resolves.toStrictEqual({ a: '1' });

return Response.json({});
}
Expand All @@ -502,7 +503,7 @@ describe('::testApiHandler', () => {
},
appHandler: {
async GET(_request, { params }) {
expect(params).toStrictEqual({ a: '1', b: '2', c: '3' });
await expect(params).resolves.toStrictEqual({ a: '1', b: '2', c: '3' });
return Response.json({});
}
},
Expand All @@ -520,8 +521,8 @@ describe('::testApiHandler', () => {
return { ...parameters, a: '1', b: '2', c: '3' };
},
appHandler: {
GET(_request, { params }) {
expect(params).toStrictEqual({
async GET(_request, { params }) {
await expect(params).resolves.toStrictEqual({
a: '1',
b: '2',
c: '3',
Expand All @@ -546,8 +547,8 @@ describe('::testApiHandler', () => {
return { a: '1', b: '2', c: '3' };
},
appHandler: {
GET(_request, { params }) {
expect(params).toStrictEqual({
async GET(_request, { params }) {
await expect(params).resolves.toStrictEqual({
a: '1',
b: '2',
c: '3'
Expand All @@ -570,8 +571,8 @@ describe('::testApiHandler', () => {
return undefined;
},
appHandler: {
GET(_request, { params }) {
expect(params).toStrictEqual({
async GET(_request, { params }) {
await expect(params).resolves.toStrictEqual({
a: 'a'
});

Expand All @@ -588,8 +589,8 @@ describe('::testApiHandler', () => {
return undefined;
},
appHandler: {
GET(_request, { params }) {
expect(params).toStrictEqual({
async GET(_request, { params }) {
await expect(params).resolves.toStrictEqual({
a: 'a'
});

Expand All @@ -603,8 +604,8 @@ describe('::testApiHandler', () => {
rejectOnHandlerError: true,
paramsPatcher: () => ({ obj: 'ect' }),
appHandler: {
GET(_request, { params }) {
expect(params).toStrictEqual({
async GET(_request, { params }) {
await expect(params).resolves.toStrictEqual({
obj: 'ect'
});

Expand All @@ -620,8 +621,8 @@ describe('::testApiHandler', () => {
return Promise.resolve({ obj: 'ect' });
},
appHandler: {
GET(_request, { params }) {
expect(params).toStrictEqual({
async GET(_request, { params }) {
await expect(params).resolves.toStrictEqual({
obj: 'ect'
});

Expand Down Expand Up @@ -702,8 +703,8 @@ describe('::testApiHandler', () => {
return undefined;
},
appHandler: {
GET(_request, { params }) {
expect(params).toStrictEqual({ d: 'z', e: 'f' });
async GET(_request, { params }) {
await expect(params).resolves.toStrictEqual({ d: 'z', e: 'f' });
return Response.json({});
}
},
Expand Down Expand Up @@ -901,8 +902,9 @@ describe('::testApiHandler', () => {
});

expect(request.nextUrl.protocol).toBe('ntarh:');
expect(request.ip).toBeFalsy();
expect(request.geo).toBeEmptyObject();
// * https://github.com/vercel/next.js/pull/68379
//expect(request.ip).toBeFalsy();
//expect(request.geo).toBeEmptyObject();

return new Response();
}
Expand Down Expand Up @@ -958,7 +960,9 @@ describe('::testApiHandler', () => {
appHandler: {
async GET() {
return Response.json({
// @ts-expect-error: canary next@15 types are wrong
c: cookies().get('__c')?.value,
// @ts-expect-error: canary next@15 types are wrong
h: headers().get('__h')
});
}
Expand Down

0 comments on commit a73f21e

Please sign in to comment.