Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement new core test API #44086

Merged
merged 3 commits into from
Dec 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 16 additions & 46 deletions test/e2e/app-dir/head.test.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,21 @@
import fs from 'fs-extra'
import path from 'path'
import cheerio from 'cheerio'
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { renderViaHTTP } from 'next-test-utils'
import webdriver from 'next-webdriver'
import { createNextDescribe } from 'e2e-utils'
import escapeStringRegexp from 'escape-string-regexp'

describe('app dir head', () => {
if ((global as any).isNextDeploy) {
it('should skip next deploy for now', () => {})
return
}

if (process.env.NEXT_TEST_REACT_VERSION === '^17') {
it('should skip for react v17', () => {})
return
}
let next: NextInstance

function runTests() {
beforeAll(async () => {
next = await createNext({
files: new FileRef(path.join(__dirname, 'head')),
dependencies: {
react: 'latest',
'react-dom': 'latest',
},
skipStart: true,
})

await next.start()
})
afterAll(() => next.destroy())

createNextDescribe(
'app dir head',
{
files: path.join(__dirname, 'head'),
skipDeployment: true,
},
({ next }) => {
it('should use head from index page', async () => {
const html = await renderViaHTTP(next.url, '/')
const $ = cheerio.load(html)
const $ = await next.render$('/')
const headTags = $('head').children().toArray()

// should not include default tags in page with head.js provided
expect(html).not.toContain(
expect($.html()).not.toContain(
'<meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/>'
)
expect(headTags.find((el) => el.attribs.src === '/hello.js')).toBeTruthy()
Expand All @@ -50,8 +25,7 @@ describe('app dir head', () => {
})

it('should use correct head for /blog', async () => {
const html = await renderViaHTTP(next.url, '/blog')
const $ = cheerio.load(html)
const $ = await next.render$('/blog')
const headTags = $('head').children().toArray()

expect(headTags.find((el) => el.attribs.src === '/hello3.js')).toBeFalsy()
Expand All @@ -67,8 +41,7 @@ describe('app dir head', () => {
})

it('should use head from layout when not on page', async () => {
const html = await renderViaHTTP(next.url, '/blog/about')
const $ = cheerio.load(html)
const $ = await next.render$('/blog/about')
const headTags = $('head').children().toArray()

expect(
Expand All @@ -83,8 +56,7 @@ describe('app dir head', () => {
})

it('should pass params to head for dynamic path', async () => {
const html = await renderViaHTTP(next.url, '/blog/post-1')
const $ = cheerio.load(html)
const $ = await next.render$('/blog/post-1')
const headTags = $('head').children().toArray()

expect(
Expand All @@ -100,7 +72,7 @@ describe('app dir head', () => {
})

it('should apply head when navigating client-side', async () => {
const browser = await webdriver(next.url, '/')
const browser = await next.browser('/')

const getTitle = () => browser.elementByCss('title').text()

Expand All @@ -125,7 +97,7 @@ describe('app dir head', () => {
next.on('stderr', (args) => {
errors.push(args)
})
const html = await renderViaHTTP(next.url, '/next-head')
const html = await next.render('/next-head')
expect(html).not.toMatch(/<title>legacy-head<\/title>/)

if (globalThis.isNextDev) {
Expand Down Expand Up @@ -155,6 +127,4 @@ describe('app dir head', () => {
}
})
}

runTests()
})
)
54 changes: 54 additions & 0 deletions test/lib/e2e-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,57 @@ export async function createNext(
flushAllTraces()
}
}

export function createNextDescribe(
name: string,
options: Parameters<typeof createNext>[0] & {
skipDeployment?: boolean
dir?: string
},
fn: (context: {
isNextDev: boolean
isNextDeploy: boolean
isNextStart: boolean
next: NextInstance
}) => void
): void {
describe(name, () => {
if (options.skipDeployment) {
// When the environment is running for deployment tests.
if ((global as any).isNextDeploy) {
it('should skip next deploy', () => {})
// No tests are run.
return
}
}

let next: NextInstance
beforeAll(async () => {
next = await createNext(options)
})
afterAll(async () => {
await next.destroy()
})

const nextProxy = new Proxy<NextInstance>({} as NextInstance, {
get: function (_target, property) {
return next[property]
},
})
fn({
get isNextDev(): boolean {
return Boolean((global as any).isNextDev)
},

get isNextDeploy(): boolean {
return Boolean((global as any).isNextDeploy)
},
get isNextStart(): boolean {
return Boolean((global as any).isNextStart)
},
get next() {
return nextProxy
},
})
})
}
67 changes: 60 additions & 7 deletions test/lib/next-modes/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import { FileRef } from '../e2e-utils'
import { ChildProcess } from 'child_process'
import { createNextInstall } from '../create-next-install'
import { Span } from 'next/trace'
import webdriver from 'next-webdriver'
import { renderViaHTTP, fetchViaHTTP } from 'next-test-utils'
import cheerio from 'cheerio'

type Event = 'stdout' | 'stderr' | 'error' | 'destroy'
export type InstallCommand =
Expand All @@ -18,7 +21,7 @@ export type PackageJson = {
[key: string]: unknown
}
export interface NextInstanceOpts {
files: FileRef | { [filename: string]: string | FileRef }
files: FileRef | string | { [filename: string]: string | FileRef }
dependencies?: { [name: string]: string }
packageJson?: PackageJson
packageLockPath?: string
Expand All @@ -31,6 +34,16 @@ export interface NextInstanceOpts {
turbo?: boolean
}

/**
* Omit the first argument of a function
*/
type OmitFirstArgument<F> = F extends (
firstArgument: any,
...args: infer P
) => infer R
? (...args: P) => R
: never

export class NextInstance {
protected files: FileRef | { [filename: string]: string | FileRef }
protected nextConfig?: NextConfig
Expand All @@ -57,20 +70,23 @@ export class NextInstance {
}

protected async writeInitialFiles() {
if (this.files instanceof FileRef) {
// Handle case where files is a directory string
const files =
typeof this.files === 'string' ? new FileRef(this.files) : this.files
if (files instanceof FileRef) {
// if a FileRef is passed directly to `files` we copy the
// entire folder to the test directory
const stats = await fs.stat(this.files.fsPath)
const stats = await fs.stat(files.fsPath)

if (!stats.isDirectory()) {
throw new Error(
`FileRef passed to "files" in "createNext" is not a directory ${this.files.fsPath}`
`FileRef passed to "files" in "createNext" is not a directory ${files.fsPath}`
)
}
await fs.copy(this.files.fsPath, this.testDir)
await fs.copy(files.fsPath, this.testDir)
} else {
for (const filename of Object.keys(this.files)) {
const item = this.files[filename]
for (const filename of Object.keys(files)) {
const item = files[filename]
const outputFilename = path.join(this.testDir, filename)

if (typeof item === 'string') {
Expand Down Expand Up @@ -363,6 +379,43 @@ export class NextInstance {
return fs.remove(path.join(this.testDir, filename))
}

/**
* Create new browser window for the Next.js app.
*/
public async browser(
...args: Parameters<OmitFirstArgument<typeof webdriver>>
) {
return webdriver(this.url, ...args)
}

/**
* Fetch the HTML for the provided page. This is a shortcut for `renderViaHTTP().then(html => cheerio.load(html))`.
*/
public async render$(
...args: Parameters<OmitFirstArgument<typeof renderViaHTTP>>
): Promise<ReturnType<typeof cheerio.load>> {
const html = await renderViaHTTP(this.url, ...args)
return cheerio.load(html)
}

/**
* Fetch the HTML for the provided page. This is a shortcut for `fetchViaHTTP().then(res => res.text())`.
*/
public async render(
...args: Parameters<OmitFirstArgument<typeof renderViaHTTP>>
) {
return renderViaHTTP(this.url, ...args)
}

/**
* Fetch the HTML for the provided page.
*/
public async fetch(
...args: Parameters<OmitFirstArgument<typeof fetchViaHTTP>>
) {
return fetchViaHTTP(this.url, ...args)
}

public on(event: Event, cb: (...args: any[]) => any) {
if (!this.events[event]) {
this.events[event] = new Set()
Expand Down