mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
662b0355eb
Test fixtures that depend on monorepo packages (for example `@next/third-parties` and `@next/mdx`) declared them at the `canary` dist-tag, which installs the published npm canary instead of the build under test. In deploy tests this also broke the remote install entirely: the published canary's `peerDependencies` ranges (for example `^16.0.0-beta.0`) reject the prerelease preview versions that `NEXT_TEST_VERSION` installs for `next` (for example `16.4.0-preview-<sha>-<date>`), so `npm install` failed with ERESOLVE. Test dependencies can now be declared as `workspace:*`, which resolves to the build from the current checkout in every test mode: - dev/start: the locally packed tarball from `pack-for-isolated-tests`, with a descriptive error when the package has no pack task or is not part of the repository. - deploy: the preview tarball of the tested commit when `NEXT_TEST_VERSION` points at a preview build (preview tarballs already rewrite their monorepo peer dependencies to the same preview URLs, so peer resolution succeeds), falling back to the version in the worktree otherwise. Private packages without published preview tarballs fail with a descriptive error. All existing tests that used `canary` for monorepo packages are migrated to `workspace:*`.
386 lines
10 KiB
TypeScript
386 lines
10 KiB
TypeScript
/* eslint-env jest */
|
|
import { createSandbox } from 'development-sandbox'
|
|
import { FileRef, nextTestSetup } from 'e2e-utils'
|
|
import path from 'path'
|
|
import { check } from 'next-test-utils'
|
|
import { outdent } from 'outdent'
|
|
|
|
describe('ReactRefreshRegression app', () => {
|
|
const { next } = nextTestSetup({
|
|
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
|
|
dependencies: {
|
|
'styled-components': '6.1.16',
|
|
'@next/mdx': 'workspace:*',
|
|
'@mdx-js/loader': '2.2.1',
|
|
'@mdx-js/react': '2.2.1',
|
|
},
|
|
skipStart: true,
|
|
})
|
|
|
|
// https://github.com/vercel/next.js/issues/12422
|
|
// TODO-APP: port to app directory
|
|
test.skip('styled-components hydration mismatch', async () => {
|
|
const files = new Map()
|
|
files.set(
|
|
'pages/_document.js',
|
|
outdent`
|
|
import Document from 'next/document'
|
|
import { ServerStyleSheet } from 'styled-components'
|
|
|
|
export default class MyDocument extends Document {
|
|
static async getInitialProps(ctx) {
|
|
const sheet = new ServerStyleSheet()
|
|
const originalRenderPage = ctx.renderPage
|
|
|
|
try {
|
|
ctx.renderPage = () =>
|
|
originalRenderPage({
|
|
enhanceApp: App => props => sheet.collectStyles(<App {...props} />),
|
|
})
|
|
|
|
const initialProps = await Document.getInitialProps(ctx)
|
|
return {
|
|
...initialProps,
|
|
styles: (
|
|
<>
|
|
{initialProps.styles}
|
|
{sheet.getStyleElement()}
|
|
</>
|
|
),
|
|
}
|
|
} finally {
|
|
sheet.seal()
|
|
}
|
|
}
|
|
}
|
|
`
|
|
)
|
|
|
|
await using sandbox = await createSandbox(next, files)
|
|
const { session } = sandbox
|
|
|
|
// We start here.
|
|
await session.patch(
|
|
'index.js',
|
|
outdent`
|
|
import React from 'react'
|
|
import styled from 'styled-components'
|
|
|
|
const Title = styled.h1\`
|
|
color: red;
|
|
font-size: 50px;
|
|
\`
|
|
|
|
export default () => <Title>My page</Title>
|
|
`
|
|
)
|
|
|
|
// Verify no hydration mismatch:
|
|
await session.waitForNoRedbox()
|
|
})
|
|
|
|
// https://github.com/vercel/next.js/issues/13978
|
|
test('can fast refresh a page with static generation', async () => {
|
|
await using sandbox = await createSandbox(next)
|
|
const { session } = sandbox
|
|
|
|
await session.patch(
|
|
'app/page.js',
|
|
outdent`
|
|
'use client'
|
|
import { useCallback, useState } from 'react'
|
|
|
|
export default function Index() {
|
|
const [count, setCount] = useState(0)
|
|
const increment = useCallback(() => setCount(c => c + 1), [setCount])
|
|
return (
|
|
<main>
|
|
<p>{count}</p>
|
|
<button onClick={increment}>Increment</button>
|
|
</main>
|
|
)
|
|
}
|
|
`
|
|
)
|
|
|
|
expect(
|
|
await session.evaluate(() => document.querySelector('p').textContent)
|
|
).toBe('0')
|
|
await session.evaluate(() => document.querySelector('button').click())
|
|
expect(
|
|
await session.evaluate(() => document.querySelector('p').textContent)
|
|
).toBe('1')
|
|
|
|
await session.patch(
|
|
'app/page.js',
|
|
outdent`
|
|
'use client'
|
|
import { useCallback, useState } from 'react'
|
|
|
|
export default function Index() {
|
|
const [count, setCount] = useState(0)
|
|
const increment = useCallback(() => setCount(c => c + 1), [setCount])
|
|
return (
|
|
<main>
|
|
<p>Count: {count}</p>
|
|
<button onClick={increment}>Increment</button>
|
|
</main>
|
|
)
|
|
}
|
|
`
|
|
)
|
|
|
|
expect(
|
|
await session.evaluate(() => document.querySelector('p').textContent)
|
|
).toBe('Count: 1')
|
|
await session.evaluate(() => document.querySelector('button').click())
|
|
expect(
|
|
await session.evaluate(() => document.querySelector('p').textContent)
|
|
).toBe('Count: 2')
|
|
})
|
|
|
|
// https://github.com/vercel/next.js/issues/13978
|
|
test('can fast refresh a page with dynamic rendering', async () => {
|
|
await using sandbox = await createSandbox(next)
|
|
const { session } = sandbox
|
|
|
|
await session.patch(
|
|
'app/page.js',
|
|
outdent`
|
|
export const revalidate = 0
|
|
|
|
import Component from '../index'
|
|
export default function Page() {
|
|
return <Component />
|
|
}
|
|
`
|
|
)
|
|
await session.patch(
|
|
'index.js',
|
|
outdent`
|
|
'use client'
|
|
import { useCallback, useState } from 'react'
|
|
|
|
export default function Index() {
|
|
const [count, setCount] = useState(0)
|
|
const increment = useCallback(() => setCount(c => c + 1), [setCount])
|
|
return (
|
|
<main>
|
|
<p>{count}</p>
|
|
<button onClick={increment}>Increment</button>
|
|
</main>
|
|
)
|
|
}
|
|
`
|
|
)
|
|
|
|
await check(
|
|
() => session.evaluate(() => document.querySelector('p').textContent),
|
|
'0'
|
|
)
|
|
|
|
await session.evaluate(() => document.querySelector('button').click())
|
|
|
|
await check(
|
|
() => session.evaluate(() => document.querySelector('p').textContent),
|
|
'1'
|
|
)
|
|
|
|
await session.patch(
|
|
'index.js',
|
|
outdent`
|
|
'use client'
|
|
import { useCallback, useState } from 'react'
|
|
|
|
export default function Index() {
|
|
const [count, setCount] = useState(0)
|
|
const increment = useCallback(() => setCount(c => c + 1), [setCount])
|
|
return (
|
|
<main>
|
|
<p>Count: {count}</p>
|
|
<button onClick={increment}>Increment</button>
|
|
</main>
|
|
)
|
|
}
|
|
`
|
|
)
|
|
|
|
await check(
|
|
() => session.evaluate(() => document.querySelector('p').textContent),
|
|
'Count: 1'
|
|
)
|
|
|
|
await session.evaluate(() => document.querySelector('button').click())
|
|
|
|
await check(
|
|
() => session.evaluate(() => document.querySelector('p').textContent),
|
|
'Count: 2'
|
|
)
|
|
})
|
|
|
|
// https://github.com/vercel/next.js/issues/13978
|
|
test('can fast refresh a page with config', async () => {
|
|
await using sandbox = await createSandbox(next)
|
|
const { session } = sandbox
|
|
|
|
await session.patch(
|
|
'app/page.js',
|
|
outdent`
|
|
export const config = {}
|
|
|
|
import Component from '../index'
|
|
export default function Page() {
|
|
return <Component />
|
|
}
|
|
`
|
|
)
|
|
|
|
await session.patch(
|
|
'index.js',
|
|
outdent`
|
|
'use client'
|
|
import { useCallback, useState } from 'react'
|
|
|
|
export const config = {}
|
|
|
|
export default function Index() {
|
|
const [count, setCount] = useState(0)
|
|
const increment = useCallback(() => setCount(c => c + 1), [setCount])
|
|
return (
|
|
<main>
|
|
<p>{count}</p>
|
|
<button onClick={increment}>Increment</button>
|
|
</main>
|
|
)
|
|
}
|
|
`
|
|
)
|
|
|
|
await check(
|
|
() => session.evaluate(() => document.querySelector('p').textContent),
|
|
'0'
|
|
)
|
|
|
|
await session.evaluate(() => document.querySelector('button').click())
|
|
expect(
|
|
await session.evaluate(() => document.querySelector('p').textContent)
|
|
).toBe('1')
|
|
})
|
|
|
|
// https://github.com/vercel/next.js/issues/11504
|
|
test('shows an overlay for anonymous function server-side error', async () => {
|
|
await using sandbox = await createSandbox(next)
|
|
const { session } = sandbox
|
|
|
|
await session.patch(
|
|
'app/page.js',
|
|
`export default function () { throw new Error('boom'); }`
|
|
)
|
|
|
|
await session.waitForRedbox()
|
|
|
|
const source = await session.getRedboxSource()
|
|
expect(source.split(/\r?\n/g).slice(2).join('\n').replace(/^\n+/, ''))
|
|
.toMatchInlineSnapshot(`
|
|
"> 1 | export default function () { throw new Error('boom'); }
|
|
| ^"
|
|
`)
|
|
})
|
|
|
|
test('shows an overlay for server-side error in server component', async () => {
|
|
await using sandbox = await createSandbox(next)
|
|
const { session } = sandbox
|
|
|
|
await session.patch(
|
|
'app/page.js',
|
|
`export default function Page() { throw new Error('boom'); }`
|
|
)
|
|
|
|
await session.waitForRedbox()
|
|
|
|
const source = await session.getRedboxSource()
|
|
expect(source.split(/\r?\n/g).slice(2).join('\n').replace(/^\n+/, ''))
|
|
.toMatchInlineSnapshot(`
|
|
"> 1 | export default function Page() { throw new Error('boom'); }
|
|
| ^"
|
|
`)
|
|
})
|
|
|
|
test('shows an overlay for server-side error in client component', async () => {
|
|
await using sandbox = await createSandbox(next)
|
|
const { session } = sandbox
|
|
|
|
await session.patch(
|
|
'app/page.js',
|
|
outdent`
|
|
'use client'
|
|
export default function Page() { throw new Error('boom'); }
|
|
`
|
|
)
|
|
|
|
await session.waitForRedbox()
|
|
|
|
const source = await session.getRedboxSource()
|
|
expect(source.split(/\r?\n/g).slice(2).join('\n').replace(/^\n+/, ''))
|
|
.toMatchInlineSnapshot(`
|
|
" 1 | 'use client'
|
|
> 2 | export default function Page() { throw new Error('boom'); }
|
|
| ^"
|
|
`)
|
|
})
|
|
|
|
// https://github.com/vercel/next.js/issues/13574
|
|
test('custom loader mdx should have Fast Refresh enabled', async () => {
|
|
const files = new Map()
|
|
files.set(
|
|
'next.config.js',
|
|
outdent`
|
|
const withMDX = require("@next/mdx")({
|
|
extension: /\\.mdx?$/,
|
|
});
|
|
module.exports = withMDX({
|
|
pageExtensions: ["js", "mdx"],
|
|
});
|
|
`
|
|
)
|
|
files.set('app/content.mdx', `Hello World!`)
|
|
files.set(
|
|
'app/page.js',
|
|
outdent`
|
|
'use client'
|
|
import MDX from './content.mdx'
|
|
export default function Page() {
|
|
return <div id="content"><MDX /></div>
|
|
}
|
|
`
|
|
)
|
|
|
|
await using sandbox = await createSandbox(next, files)
|
|
const { session } = sandbox
|
|
|
|
expect(
|
|
await session.evaluate(
|
|
() => document.querySelector('#content').textContent
|
|
)
|
|
).toBe('Hello World!')
|
|
|
|
let didNotReload = await session.patch('app/content.mdx', `Hello Foo!`)
|
|
expect(didNotReload).toBe(true)
|
|
await session.waitForNoRedbox()
|
|
expect(
|
|
await session.evaluate(
|
|
() => document.querySelector('#content').textContent
|
|
)
|
|
).toBe('Hello Foo!')
|
|
|
|
didNotReload = await session.patch('app/content.mdx', `Hello Bar!`)
|
|
expect(didNotReload).toBe(true)
|
|
await session.waitForNoRedbox()
|
|
expect(
|
|
await session.evaluate(
|
|
() => document.querySelector('#content').textContent
|
|
)
|
|
).toBe('Hello Bar!')
|
|
})
|
|
})
|