Skip to content

Commit

Permalink
type check tests (and convert next-test-utils.js to ts) (#51071)
Browse files Browse the repository at this point in the history
Enables type checking for tests in CI and fixes a bunch of things related to that
  • Loading branch information
ForsakenHarmony committed Jun 23, 2023
1 parent d457e98 commit 5d54eaa
Show file tree
Hide file tree
Showing 34 changed files with 415 additions and 253 deletions.
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"genstats": "cross-env LOCAL_STATS=true node .github/actions/next-stats-action/src/index.js",
"git-reset": "git reset --hard HEAD",
"git-clean": "git clean -d -x -e node_modules -e packages -f",
"typescript": "tsc --noEmit",
"lint-typescript": "turbo run typescript",
"lint-eslint": "eslint . --ext js,jsx,ts,tsx --max-warnings=0 --config .eslintrc.json --no-eslintrc",
"lint-no-typescript": "run-p prettier-check lint-eslint lint-language",
Expand Down Expand Up @@ -84,7 +85,10 @@
"@swc/helpers": "0.5.1",
"@testing-library/react": "13.0.0",
"@types/cheerio": "0.22.16",
"@types/cookie": "0.3.3",
"@types/cross-spawn": "6.0.0",
"@types/fs-extra": "8.1.0",
"@types/glob": "7.1.1",
"@types/html-validator": "5.0.3",
"@types/http-proxy": "1.17.3",
"@types/jest": "24.0.13",
Expand Down Expand Up @@ -171,6 +175,7 @@
"open": "9.0.0",
"outdent": "0.8.0",
"pixrem": "5.0.0",
"playwright-core": "1.28.1",
"playwright-chromium": "1.28.1",
"plop": "3.0.5",
"postcss": "8.4.14",
Expand Down
4 changes: 2 additions & 2 deletions packages/next/src/lib/load-custom-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,11 @@ function checkRedirect(route: Redirect): {
const invalidParts: string[] = []
let hadInvalidStatus: boolean = false

if (route.statusCode && !allowedStatusCodes.has(route.statusCode)) {
if (route.statusCode && !allowedStatusCodes.has(route['statusCode'])) {
hadInvalidStatus = true
invalidParts.push(`\`statusCode\` is not undefined or valid statusCode`)
}
if (typeof route.permanent !== 'boolean' && !route.statusCode) {
if (typeof route.permanent !== 'boolean' && !route['statusCode']) {
invalidParts.push(`\`permanent\` is not set to \`true\` or \`false\``)
}

Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/lib/patch-incorrect-lockfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export async function patchIncorrectLockfile(dir: string) {

const lockfileParsed = JSON.parse(content)
const lockfileVersion = parseInt(lockfileParsed?.lockfileVersion, 10)
const expectedSwcPkgs = Object.keys(nextPkgJson.optionalDependencies || {})
const expectedSwcPkgs = Object.keys(nextPkgJson['optionalDependencies'] || {})

const patchDependency = (
pkg: string,
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion test/e2e/app-dir/actions/app-action.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ createNextDescribe(

it('should support setting cookies in route handlers with the correct overrides', async () => {
const res = await next.fetch('/handler')
const setCookieHeader = res.headers.get('set-cookie') as any as string[]
const setCookieHeader = res.headers.get('set-cookie')
expect(setCookieHeader).toContain('bar=bar2; Path=/')
expect(setCookieHeader).toContain('baz=baz2; Path=/')
expect(setCookieHeader).toContain('foo=foo1; Path=/')
Expand Down
1 change: 1 addition & 0 deletions test/e2e/app-dir/app-middleware/app-middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import path from 'path'
import cheerio from 'cheerio'
import { check, withQuery } from 'next-test-utils'
import { createNextDescribe, FileRef } from 'e2e-utils'
import type { Response } from 'node-fetch'

createNextDescribe(
'app-dir with middleware',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import type { Request } from 'playwright-core'

import { createNextDescribe } from 'e2e-utils'
import type { BrowserInterface } from '../../../lib/browsers/base'

const getPathname = (url: string) => {
const urlObj = new URL(url)
Expand Down
7 changes: 6 additions & 1 deletion test/e2e/app-dir/app-routes/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,12 @@ type Cookies = {
* @returns any injected metadata on the request
*/
export function getRequestMeta(
headersOrCookies: Headers | Cookies | ReadonlyHeaders | ReadonlyRequestCookies
headersOrCookies:
| Headers
| import('node-fetch').Headers
| Cookies
| ReadonlyHeaders
| ReadonlyRequestCookies
): Record<string, any> {
const headerOrCookie = headersOrCookies.get(KEY)
if (!headerOrCookie) return {}
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/app-dir/app/standalone.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ if (!(globalThis as any).isNextStart) {
/Listening on/,
{
...process.env,
PORT: appPort,
PORT: appPort.toString(),
},
undefined,
{
Expand Down
3 changes: 2 additions & 1 deletion test/e2e/app-dir/set-cookies/set-cookies.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { createNextDescribe } from 'e2e-utils'
import type { Response } from 'node-fetch'

import cookies, { nextConfigHeaders } from './cookies.mjs'

function getSetCookieHeaders(res: globalThis.Response): ReadonlyArray<string> {
function getSetCookieHeaders(res: Response): ReadonlyArray<string> {
return (
(res.headers as any).getSetCookie?.() ??
(res.headers as any).raw()['set-cookie']
Expand Down
1 change: 1 addition & 0 deletions test/e2e/edge-can-read-request-body/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { NextInstance } from 'test/lib/next-modes/base'
import { fetchViaHTTP, renderViaHTTP } from 'next-test-utils'
import FormData from 'form-data'
import path from 'path'
import type { Response } from 'node-fetch'

async function serialize(response: Response) {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { NextInstance } from 'test/lib/next-modes/base'
import { fetchViaHTTP } from 'next-test-utils'
import { createNext, FileRef } from 'e2e-utils'
import cheerio from 'cheerio'
import type { Response } from 'node-fetch'

describe('Middleware Request Headers Overrides', () => {
let next: NextInstance
Expand Down
6 changes: 3 additions & 3 deletions test/e2e/next-font/with-proxy.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { FileRef, createNext, NextInstance } from 'e2e-utils'
import { findPort, renderViaHTTP, fetchViaHTTP } from 'next-test-utils'
import { join } from 'path'
import { spawn } from 'cross-spawn'
import spawn from 'cross-spawn'

describe('next/font/google with proxy', () => {
let next: NextInstance
Expand All @@ -22,8 +22,8 @@ describe('next/font/google with proxy', () => {
stdio: 'inherit',
env: {
...process.env,
PROXY_PORT,
SERVER_PORT,
PROXY_PORT: PROXY_PORT.toString(),
SERVER_PORT: SERVER_PORT.toString(),
},
})

Expand Down
4 changes: 2 additions & 2 deletions test/e2e/prerender.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1850,8 +1850,8 @@ describe('Prerender', () => {
previewRes.headers
.get('set-cookie')
.split(',')
.forEach((c) => {
c = cookie.parse(c)
.forEach((s) => {
const c = cookie.parse(s)
const isBypass = c.__prerender_bypass

if (isBypass || c.__next_preview_data) {
Expand Down
1 change: 1 addition & 0 deletions test/integration/config-output-export/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import webdriver from 'next-webdriver'
import { join } from 'path'
import fs from 'fs'
import type { Response } from 'node-fetch'

const appDir = join(__dirname, '../')
const nextConfig = new File(join(appDir, 'next.config.js'))
Expand Down
11 changes: 7 additions & 4 deletions test/integration/draft-mode/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ describe('Test Draft Mode', () => {
const res = await fetchViaHTTP(appPort, '/api/enable')
expect(res.status).toBe(200)

const cookies = res.headers.get('set-cookie').split(',').map(cookie.parse)
const cookies = res.headers
.get('set-cookie')
.split(',')
.map((c) => cookie.parse(c))

expect(cookies[0]).toBeTruthy()
expect(cookies[0].__prerender_bypass).toBeTruthy()
Expand Down Expand Up @@ -76,7 +79,7 @@ describe('Test Draft Mode', () => {
.get('set-cookie')
.replace(/(=(?!Lax)\w{3}),/g, '$1')
.split(',')
.map(cookie.parse)
.map((c) => cookie.parse(c))

expect(cookies[0]).toBeTruthy()
})
Expand Down Expand Up @@ -154,7 +157,7 @@ describe('Test Draft Mode', () => {
expect(res.status).toBe(200)

const originalCookies = res.headers.get('set-cookie').split(',')
const cookies = originalCookies.map(cookie.parse)
const cookies = originalCookies.map((c) => cookie.parse(c))

expect(cookies.length).toBe(1)
expect(cookies[0]).toBeTruthy()
Expand Down Expand Up @@ -212,7 +215,7 @@ describe('Test Draft Mode', () => {
.get('set-cookie')
.replace(/(=(?!Lax)\w{3}),/g, '$1')
.split(',')
.map(cookie.parse)
.map((c) => cookie.parse(c))

expect(cookies[0]).toBeTruthy()
expect(cookies[0]).toMatchObject({
Expand Down
16 changes: 14 additions & 2 deletions test/integration/draft-mode/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,20 @@
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
"jsx": "preserve",
"baseUrl": "../../..",
"paths": {
"development-sandbox": ["test/lib/development-sandbox"],
"next-test-utils": ["test/lib/next-test-utils"],
"amp-test-utils": ["test/lib/amp-test-utils"],
"next-webdriver": ["test/lib/next-webdriver"],
"e2e-utils": ["test/lib/e2e-utils"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"include": [
"test/integration/draft-mode/next-env.d.ts",
"test/integration/draft-mode/**/*.ts",
"test/integration/draft-mode/**/*.tsx"
],
"exclude": ["node_modules"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ describe('dev mode', () => {
context.appPort = await findPort()
context.app = await launchApp(appDir, context.appPort, {
...context.handler,
env: { __NEXT_TEST_WITH_DEVTOOL: 1 },
env: { __NEXT_TEST_WITH_DEVTOOL: '1' },
})
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ describe.each([
output = ''
appPort = await findPort()
app = await launchApp(appDir, appPort, {
env: { __NEXT_TEST_WITH_DEVTOOL: 1 },
env: { __NEXT_TEST_WITH_DEVTOOL: '1' },
onStdout(msg) {
output += msg
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ describe('TypeScript Image Component', () => {
nextConfig,
content.replace('// disableStaticImages', 'disableStaticImages')
)
const app = await launchApp(appDir, await findPort(), [])
const app = await launchApp(appDir, await findPort())
await killApp(app)
await fs.writeFile(nextConfig, content)
const envTypes = await fs.readFile(join(appDir, 'next-env.d.ts'), 'utf8')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ describe('TypeScript Image Component', () => {
nextConfig,
content.replace('// disableStaticImages', 'disableStaticImages')
)
const app = await launchApp(appDir, await findPort(), [])
const app = await launchApp(appDir, await findPort())
await killApp(app)
await fs.writeFile(nextConfig, content)
const envTypes = await fs.readFile(join(appDir, 'next-env.d.ts'), 'utf8')
Expand Down
1 change: 1 addition & 0 deletions test/jest-setup-after-env.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-ignore
import * as matchers from 'jest-extended'
expect.extend(matchers)

Expand Down
28 changes: 27 additions & 1 deletion test/jest.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,27 @@
import 'jest-extended'
/// <reference types="jest" />
/// <reference types="jest-extended" />

declare namespace jest {
// https://github.com/jestjs/jest/blob/6460335f88cee3dcb9d29c49d55ab02b9d83f994/packages/expect/src/types.ts#L58-L72
interface MatcherState {
assertionCalls: number
currentTestName?: string
error?: Error
expand?: boolean
expectedAssertionsNumber: number | null
expectedAssertionsNumberError?: Error
isExpectingAssertions: boolean
isExpectingAssertionsError?: Error
isNot?: boolean
numPassingAsserts: number
promise?: string
suppressedErrors: Array<Error>
testPath?: string
}

interface Expect {
// https://github.com/jestjs/jest/blob/6460335f88cee3dcb9d29c49d55ab02b9d83f994/packages/expect/src/index.ts#L461
// https://github.com/jestjs/jest/blob/6460335f88cee3dcb9d29c49d55ab02b9d83f994/packages/expect/src/jestMatchersObject.ts#L44-L45
getState(): MatcherState
}
}
19 changes: 13 additions & 6 deletions test/lib/browsers/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,29 @@ export type Event = 'request'
* methods we aim to support across tests
*
* They will always await last executed command.
* The interface is mutable - it doesn't have to be in sequece.
* The interface is mutable - it doesn't have to be in sequence.
*
* You can manually await this interface to wait for completion of the last scheduled command.
*/
export class BrowserInterface implements PromiseLike<any> {
private promise: any
then: PromiseLike<any>['then']
private catch: any
export abstract class BrowserInterface implements PromiseLike<any> {
private promise?: Promise<any>
then: Promise<any>['then']
catch: Promise<any>['catch']
finally: Promise<any>['finally'];

protected chain(nextCall: any): BrowserInterface {
// necessary for the type of the function below
readonly [Symbol.toStringTag]: string = 'BrowserInterface'

protected chain<T>(
nextCall: (current: any) => T | PromiseLike<T>
): BrowserInterface & Promise<T> {
if (!this.promise) {
this.promise = Promise.resolve(this)
}
this.promise = this.promise.then(nextCall)
this.then = (...args) => this.promise.then(...args)
this.catch = (...args) => this.promise.catch(...args)
this.finally = (...args) => this.promise.finally(...args)
return this
}

Expand Down

0 comments on commit 5d54eaa

Please sign in to comment.