Skip to content

Commit

Permalink
Merge branch 'vercel:canary' into tsec-integration
Browse files Browse the repository at this point in the history
  • Loading branch information
jgoping committed Jan 27, 2022
2 parents 5c86402 + eea3adc commit 05ce0be
Show file tree
Hide file tree
Showing 8 changed files with 230 additions and 203 deletions.
5 changes: 5 additions & 0 deletions examples/cms-datocms/next.config.js
@@ -0,0 +1,5 @@
module.exports = {
images: {
domains: ['www.datocms-assets.com'],
},
}
4 changes: 4 additions & 0 deletions examples/with-docker/Dockerfile
Expand Up @@ -6,6 +6,10 @@ WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile

# If using npm with a `package-lock.json` comment out above and use below instead
# COPY package.json package-lock.json /
# RUN npm install

# Rebuild the source code only when needed
FROM node:16-alpine AS builder
WORKDIR /app
Expand Down
2 changes: 1 addition & 1 deletion packages/next/lib/is-error.ts
@@ -1,4 +1,4 @@
import { isPlainObject } from './is-plain-object'
import { isPlainObject } from '../shared/lib/is-plain-object'

// We allow some additional attached properties for Errors
export interface NextError extends Error {
Expand Down
5 changes: 4 additions & 1 deletion packages/next/lib/is-serializable-props.ts
@@ -1,4 +1,7 @@
import { isPlainObject, getObjectClassLabel } from './is-plain-object'
import {
isPlainObject,
getObjectClassLabel,
} from '../shared/lib/is-plain-object'

const regexpPlainIdentifier = /^[A-Za-z_$][A-Za-z0-9_$]*$/

Expand Down
File renamed without changes.
@@ -1,6 +1,5 @@
/* eslint-env jest */

import cheerio from 'cheerio'
import { join } from 'path'
import fs from 'fs-extra'
import webdriver from 'next-webdriver'
Expand All @@ -14,10 +13,11 @@ import {
nextBuild as _nextBuild,
nextStart as _nextStart,
renderViaHTTP,
check,
} from 'next-test-utils'

import css from './css'
import rsc from './rsc'
import streaming from './streaming'

const nodeArgs = ['-r', join(__dirname, '../../react-18/test/require-hook.js')]
const appDir = join(__dirname, '../app')
Expand Down Expand Up @@ -123,20 +123,6 @@ async function nextDev(dir, port) {
})
}

async function resolveStreamResponse(response, onData) {
let result = ''
onData = onData || (() => {})
await new Promise((resolve) => {
response.body.on('data', (chunk) => {
result += chunk.toString()
onData(chunk.toString(), result)
})

response.body.on('end', resolve)
})
return result
}

describe('concurrentFeatures - basic', () => {
it('should warn user for experimental risk with server components', async () => {
const edgeRuntimeWarning =
Expand Down Expand Up @@ -235,8 +221,8 @@ const customAppPageSuite = {
expect(indexFlight).toContain('container-server')
})
},
before: () => appServerPage.write(rscAppPage),
after: () => appServerPage.delete(),
beforeAll: () => appServerPage.write(rscAppPage),
afterAll: () => appServerPage.delete(),
}

runSuite('Custom App', 'dev', customAppPageSuite)
Expand Down Expand Up @@ -281,8 +267,8 @@ describe('concurrentFeatures - dev', () => {

const cssSuite = {
runTests: css,
before: () => appPage.write(appWithGlobalCss),
after: () => appPage.delete(),
beforeAll: () => appPage.write(appWithGlobalCss),
afterAll: () => appPage.delete(),
}

runSuite('CSS', 'dev', cssSuite)
Expand All @@ -300,229 +286,76 @@ const documentSuite = {
)
})
},
before: () => documentPage.write(documentWithGip),
after: () => documentPage.delete(),
beforeAll: () => documentPage.write(documentWithGip),
afterAll: () => documentPage.delete(),
}

runSuite('document', 'dev', documentSuite)
runSuite('document', 'prod', documentSuite)

async function runBasicTests(context, env) {
const isDev = env === 'dev'
it('should render html correctly', async () => {
const homeHTML = await renderViaHTTP(context.appPort, '/', null, {
headers: {
'x-next-test-client': 'test-util',
},
})

// should have only 1 DOCTYPE
expect(homeHTML).toMatch(/^<!DOCTYPE html><html/)

// dynamic routes
const dynamicRouteHTML1 = await renderViaHTTP(
context.appPort,
'/routes/dynamic1'
)
const dynamicRouteHTML2 = await renderViaHTTP(
context.appPort,
'/routes/dynamic2'
)

const path404HTML = await renderViaHTTP(context.appPort, '/404')
it('should render 500 error correctly', async () => {
const path500HTML = await renderViaHTTP(context.appPort, '/err')
const pathNotFoundHTML = await renderViaHTTP(
context.appPort,
'/this-is-not-found'
)

const page404Content = 'custom-404-page'

expect(homeHTML).toContain('component:index.server')
expect(homeHTML).toContain('env:env_var_test')
expect(homeHTML).toContain('header:test-util')
expect(homeHTML).toContain('path:/')
expect(homeHTML).toContain('foo.client')

expect(dynamicRouteHTML1).toContain('query: dynamic1')
expect(dynamicRouteHTML2).toContain('query: dynamic2')

const $404 = cheerio.load(path404HTML)
expect($404('#__next').text()).toBe(page404Content)

// In dev mode: it should show the error popup.
// In dev mode it should show the error popup.
const isDev = env === 'dev'
expect(path500HTML).toContain(isDev ? 'Error: oops' : 'custom-500-page')
expect(pathNotFoundHTML).toContain(page404Content)
})

it('should disable cache for fizz pages', async () => {
const urls = ['/', '/next-api/image', '/next-api/link']
await Promise.all(
urls.map(async (url) => {
const { headers } = await fetchViaHTTP(context.appPort, url)
expect(headers.get('cache-control')).toBe(
'no-cache, no-store, max-age=0, must-revalidate'
)
})
)
})

it('should support next/link', async () => {
const linkHTML = await renderViaHTTP(context.appPort, '/next-api/link')
const $ = cheerio.load(linkHTML)
const linkText = $('div[hidden] > a[href="/"]').text()

expect(linkText).toContain('go home')

const browser = await webdriver(context.appPort, '/next-api/link')

// We need to make sure the app is fully hydrated before clicking, otherwise
// it will be a full redirection instead of being taken over by the next
// router. This timeout prevents it being flaky caused by fast refresh's
// rebuilding event.
await new Promise((res) => setTimeout(res, 1000))
await browser.eval('window.beforeNav = 1')

await browser.waitForElementByCss('#next_id').click()
await check(() => browser.elementByCss('#query').text(), 'query:1')

await browser.waitForElementByCss('#next_id').click()
await check(() => browser.elementByCss('#query').text(), 'query:2')

expect(await browser.eval('window.beforeNav')).toBe(1)
})

it('should suspense next/image on server side', async () => {
const imageHTML = await renderViaHTTP(context.appPort, '/next-api/image')
const $ = cheerio.load(imageHTML)
const imageTag = $('div[hidden] > span > span > img')
it('should render 404 error correctly', async () => {
const path404HTML = await renderViaHTTP(context.appPort, '/404')
const pathNotFoundHTML = await renderViaHTTP(context.appPort, '/not-found')

expect(imageTag.attr('src')).toContain('data:image')
expect(path404HTML).toContain('custom-404-page')
expect(pathNotFoundHTML).toContain('custom-404-page')
})

it('should handle multiple named exports correctly', async () => {
const clientExportsHTML = await renderViaHTTP(
it('should render dynamic routes correctly', async () => {
const dynamicRoute1HTML = await renderViaHTTP(
context.appPort,
'/client-exports'
)
const $clientExports = cheerio.load(clientExportsHTML)
expect($clientExports('div[hidden] > div').text()).toBe('abcde')

const browser = await webdriver(context.appPort, '/client-exports')
const text = await browser.waitForElementByCss('#__next').text()
expect(text).toBe('abcde')
})

it('should support multi-level server component imports', async () => {
const html = await renderViaHTTP(context.appPort, '/multi')
expect(html).toContain('bar.server.js:')
expect(html).toContain('foo.client')
})

it('should support streaming', async () => {
await fetchViaHTTP(context.appPort, '/streaming', null, {}).then(
async (response) => {
let gotFallback = false
let gotData = false

await resolveStreamResponse(response, (_, result) => {
gotData = result.includes('next_streaming_data')
if (!gotFallback) {
gotFallback = result.includes('next_streaming_fallback')
if (gotFallback) {
expect(gotData).toBe(false)
}
}
})

expect(gotFallback).toBe(true)
expect(gotData).toBe(true)
}
)

// Should end up with "next_streaming_data".
const browser = await webdriver(context.appPort, '/streaming')
const content = await browser.eval(`window.document.body.innerText`)
expect(content).toMatchInlineSnapshot('"next_streaming_data"')
})

it('should support streaming flight request', async () => {
await fetchViaHTTP(context.appPort, '/?__flight__=1').then(
async (response) => {
const result = await resolveStreamResponse(response)
expect(result).toContain('component:index.server')
}
'/routes/dynamic1'
)
})

it('should support partial hydration with inlined server data', async () => {
await fetchViaHTTP(context.appPort, '/partial-hydration', null, {}).then(
async (response) => {
let gotFallback = false
let gotData = false
let gotInlinedData = false

await resolveStreamResponse(response, (_, result) => {
gotInlinedData = result.includes('self.__next_s=')
gotData = result.includes('next_streaming_data')
if (!gotFallback) {
gotFallback = result.includes('next_streaming_fallback')
if (gotFallback) {
expect(gotData).toBe(false)
expect(gotInlinedData).toBe(false)
}
}
})

expect(gotFallback).toBe(true)
expect(gotData).toBe(true)
expect(gotInlinedData).toBe(true)
}
const dynamicRoute2HTML = await renderViaHTTP(
context.appPort,
'/routes/dynamic2'
)

// Should end up with "next_streaming_data".
const browser = await webdriver(context.appPort, '/partial-hydration')
const content = await browser.eval(`window.document.body.innerText`)
expect(content).toContain('next_streaming_data')

// Should support partial hydration: the boundary should still be pending
// while another part is hydrated already.
expect(await browser.eval(`window.partial_hydration_suspense_result`)).toBe(
'next_streaming_fallback'
)
expect(await browser.eval(`window.partial_hydration_counter_result`)).toBe(
'count: 1'
)
expect(dynamicRoute1HTML).toContain('query: dynamic1')
expect(dynamicRoute2HTML).toContain('query: dynamic2')
})

it('should support api routes', async () => {
const res = await renderViaHTTP(context.appPort, '/api/ping')
expect(res).toContain('pong')
})

rsc(context)
streaming(context)
}

function runSuite(suiteName, env, { runTests, before, after }) {
function runSuite(suiteName, env, options) {
const context = { appDir }
describe(`${suiteName} ${env}`, () => {
if (env === 'prod') {
beforeAll(async () => {
before?.()
options.beforeAll?.()
context.appPort = await findPort()
context.server = await nextDev(context.appDir, context.appPort)
await nextBuild(context.appDir)
context.server = await nextStart(context.appDir, context.appPort)
})
}
if (env === 'dev') {
beforeAll(async () => {
before?.()
options.beforeAll?.()
context.appPort = await findPort()
context.server = await nextDev(context.appDir, context.appPort)
})
}
afterAll(async () => {
after?.()
options.afterAll?.()
await killApp(context.server)
})

runTests(context, env)
options.runTests(context, env)
})
}

0 comments on commit 05ce0be

Please sign in to comment.