+ Forms with Next.js! +
+ +
+ Get started by looking at{' '}
+ pages/js-form.js
and{' '}
+ pages/no-js-form.js
+
diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml
index d74d4da95102b1..0d6149ba67b810 100644
--- a/.github/workflows/build_test_deploy.yml
+++ b/.github/workflows/build_test_deploy.yml
@@ -347,35 +347,6 @@ jobs:
- run: xvfb-run node run-tests.js test/integration/with-electron/test/index.test.js
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
- testYarnPnP:
- runs-on: ubuntu-latest
- needs: [build, build-native-dev]
- env:
- NODE_OPTIONS: '--unhandled-rejections=strict'
- YARN_COMPRESSION_LEVEL: '0'
- steps:
- - name: Setup node
- uses: actions/setup-node@v2
- if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }}
- with:
- node-version: 14
-
- - uses: actions/cache@v2
- if: ${{needs.build.outputs.docsChange != 'docs only change'}}
- id: restore-build
- with:
- path: ./*
- key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }}
-
- - uses: actions/download-artifact@v2
- if: ${{needs.build.outputs.docsChange != 'docs only change'}}
- with:
- name: next-swc-dev-binary
- path: packages/next-swc/native
-
- - run: bash ./scripts/test-pnp.sh
- if: ${{needs.build.outputs.docsChange != 'docs only change'}}
-
testsPass:
name: thank you, next
runs-on: ubuntu-latest
@@ -387,7 +358,6 @@ jobs:
checkPrecompiled,
testIntegration,
testUnit,
- testYarnPnP,
testDev,
testProd,
]
diff --git a/SECURITY.md b/SECURITY.md
index 294bff136eef4f..7ef6aaaaa6d0fa 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -1 +1,9 @@
-Visit https://vercel.com/security to view the disclosure policy.
+# Reporting Security Issues
+
+If you believe you have found a security vulnerability in Next.js, we encourage you to let us know right away.
+
+We will investigate all legitimate reports and do our best to quickly fix the problem.
+
+Email `security@vercel.com` to disclose any security vulnerabilities.
+
+https://vercel.com/security
diff --git a/contributing.md b/contributing.md
index 041321c7898987..1fd1133aee776d 100644
--- a/contributing.md
+++ b/contributing.md
@@ -6,10 +6,9 @@ examples](#adding-examples)** below.
## Developing
-The development branch is `canary`, and this is the branch that all pull
-requests should be made against. After publishing a stable release, the changes
-in the `canary` branch are rebased into `master`. The changes on the `canary`
-branch are published to the `@canary` dist-tag daily.
+The development branch is `canary`. This is the branch that all pull
+requests should be made against. The changes on the `canary`
+branch are published to the `@canary` tag on npm regularly.
To develop locally:
diff --git a/docs/advanced-features/compiler.md b/docs/advanced-features/compiler.md
index 58b540ebf725cd..ac07f30fe24c16 100644
--- a/docs/advanced-features/compiler.md
+++ b/docs/advanced-features/compiler.md
@@ -94,6 +94,34 @@ const customJestConfig = {
module.exports = createJestConfig(customJestConfig)
```
+### Remove React Properties
+
+Allows to remove JSX properties. This is often used for testing. Similar to `babel-plugin-react-remove-properties`.
+
+To remove properties matching the default regex `^data-test`:
+
+```js
+// next.config.js
+module.exports = {
+ experimental: {
+ reactRemoveProperties: true,
+ },
+}
+```
+
+To remove custom properties:
+
+```js
+// next.config.js
+module.exports = {
+ experimental: {
+ // The regexes defined here are processed in Rust so the syntax is different from
+ // JavaScript `RegExp`s. See https://docs.rs/regex.
+ reactRemoveProperties: { properties: ['^data-custom$'] },
+ },
+}
+```
+
### Legacy Decorators
Next.js will automatically detect `experimentalDecorators` in `jsconfig.json` or `tsconfig.json` and apply that. This is commonly used with older versions of libraries like `mobx`.
@@ -110,6 +138,34 @@ First, update to the latest version of Next.js: `npm install next@latest`. Then,
}
```
+### Remove Console
+
+This transform allows for removing all `console.*` calls in application code (not `node_modules`). Similar to `babel-plugin-transform-remove-console`.
+
+Remove all `console.*` calls:
+
+```js
+// next.config.js
+module.exports = {
+ experimental: {
+ removeConsole: true,
+ },
+}
+```
+
+Remove `console.*` output except `console.error`:
+
+```js
+// next.config.js
+module.exports = {
+ experimental: {
+ removeConsole: {
+ exclude: ['error'],
+ },
+ },
+}
+```
+
### importSource
Next.js will automatically detect `jsxImportSource` in `jsconfig.json` or `tsconfig.json` and apply that. This is commonly used with libraries like Theme UI.
diff --git a/docs/advanced-features/security-headers.md b/docs/advanced-features/security-headers.md
index c51718bdcb08e0..9a3165c0e9ead9 100644
--- a/docs/advanced-features/security-headers.md
+++ b/docs/advanced-features/security-headers.md
@@ -18,7 +18,7 @@ module.exports = {
return [
{
// Apply these headers to all routes in your application.
- source: '/(.*)',
+ source: '/:path*',
headers: securityHeaders,
},
]
diff --git a/docs/api-reference/next/script.md b/docs/api-reference/next/script.md
index 49fe558c6b1639..79c83341f88766 100644
--- a/docs/api-reference/next/script.md
+++ b/docs/api-reference/next/script.md
@@ -46,6 +46,8 @@ The loading strategy of the script.
A method that returns additional JavaScript that should be executed after the script has finished loading.
+> **Note: `onLoad` can't be used with the `beforeInteractive` loading strategy.**
+
The following is an example of how to use the `onLoad` property:
```jsx
diff --git a/docs/authentication.md b/docs/authentication.md
index 45dc5bbe185a42..9163ae9e178ac2 100644
--- a/docs/authentication.md
+++ b/docs/authentication.md
@@ -71,7 +71,9 @@ import withSession from '../lib/session'
import Layout from '../components/Layout'
export const getServerSideProps = withSession(async function ({ req, res }) {
- if (!req.session.user) {
+ const { user } = req.session
+
+ if (!user) {
return {
redirect: {
destination: '/login',
diff --git a/docs/basic-features/data-fetching/client-side.md b/docs/basic-features/data-fetching/client-side.md
index 462958b39b6a53..168bb59967def3 100644
--- a/docs/basic-features/data-fetching/client-side.md
+++ b/docs/basic-features/data-fetching/client-side.md
@@ -43,7 +43,7 @@ function Profile() {
## Client-side data fetching with SWR
-The team behind Next.js has created a React hook library for data fetching called [**SWR**](https://swr.vercel.app/). It is **highly recommend** if you are fetching data on the client-side. It handles caching, revalidation, focus tracking, refetching on intervals, and more.
+The team behind Next.js has created a React hook library for data fetching called [**SWR**](https://swr.vercel.app/). It is **highly recommended** if you are fetching data on the client-side. It handles caching, revalidation, focus tracking, refetching on intervals, and more.
Using the same example as above, we can now use SWR to fetch the profile data. SWR will automatically cache the data for us and will revalidate the data if it becomes stale.
diff --git a/errors/export-image-api.md b/errors/export-image-api.md
index dae959c90dcaf0..caedb067914793 100644
--- a/errors/export-image-api.md
+++ b/errors/export-image-api.md
@@ -2,7 +2,7 @@
#### Why This Error Occurred
-You are attempting to run `next export` while importing the `next/image` component configured using the default `loader`.
+You are attempting to run `next export` while importing the `next/image` component using the default `loader` configuration.
However, the default `loader` relies on the Image Optimization API which is not available for exported applications.
@@ -10,15 +10,15 @@ This is because Next.js optimizes images on-demand, as users request them (not a
#### Possible Ways to Fix It
-- Use `next start` to run a server, which includes the Image Optimization API.
-- Use any provider which supports Image Optimization (like [Vercel](https://vercel.com/docs/next.js/image-optimization)).
-- Configure a third-party [loader](https://nextjs.org/docs/basic-features/image-optimization#loader) in `next.config.js`.
-- Use the [`loader`](https://nextjs.org/docs/api-reference/next/image#loader) prop for `next/image`.
+- Use [`next start`](https://nextjs.org/docs/api-reference/cli#production) to run a server, which includes the Image Optimization API.
+- Use any provider which supports Image Optimization (such as [Vercel](https://vercel.com)).
+- [Configure the loader](https://nextjs.org/docs/api-reference/next/image#loader-configuration) in `next.config.js`.
+- Use the [`loader`](https://nextjs.org/docs/api-reference/next/image#loader) prop for each instance of `next/image`.
### Useful Links
-- [Deployment Documentation](https://nextjs.org/docs/deployment#vercel-recommended)
+- [Deployment Documentation](https://nextjs.org/docs/deployment#managed-nextjs-with-vercel)
- [Image Optimization Documentation](https://nextjs.org/docs/basic-features/image-optimization)
- [`next export` Documentation](https://nextjs.org/docs/advanced-features/static-html-export)
- [`next/image` Documentation](https://nextjs.org/docs/api-reference/next/image)
-- [Vercel Documentation](https://vercel.com/docs/next.js/image-optimization)
+- [Vercel Documentation](https://vercel.com/docs/concepts/next.js/image-optimization)
diff --git a/errors/manifest.json b/errors/manifest.json
index 4aaeead555a5a9..69d07630775744 100644
--- a/errors/manifest.json
+++ b/errors/manifest.json
@@ -527,6 +527,10 @@
{
"title": "invalid-styled-jsx-children",
"path": "/errors/invalid-styled-jsx-children.md"
+ },
+ {
+ "title": "middleware-relative-urls",
+ "path": "/errors/middleware-relative-urls.md"
}
]
}
diff --git a/errors/middleware-relative-urls.md b/errors/middleware-relative-urls.md
new file mode 100644
index 00000000000000..dabdf33060855b
--- /dev/null
+++ b/errors/middleware-relative-urls.md
@@ -0,0 +1,38 @@
+# Middleware Relative URLs
+
+#### Why This Error Occurred
+
+You are using a Middleware function that uses `Response.redirect(url)`, `NextResponse.redirect(url)` or `NextResponse.rewrite(url)` where `url` is a relative or an invalid URL. Currently this will work, but building a request with `new Request(url)` or running `fetch(url)` when `url` is a relative URL will **not** work. For this reason and to bring consistency to Next.js Middleware, this behavior will be deprecated soon in favor of always using absolute URLs.
+
+#### Possible Ways to Fix It
+
+To fix this warning you must always pass absolute URL for redirecting and rewriting. There are several ways to get the absolute URL but the recommended way is to clone `NextURL` and mutate it:
+
+```typescript
+import type { NextRequest } from 'next/server'
+import { NextResponse } from 'next/server'
+
+export function middleware(request: NextRequest) {
+ const url = request.nextUrl.clone()
+ url.pathname = '/dest'
+ return NextResponse.rewrite(url)
+}
+```
+
+Another way to fix this error could be to use the original URL as the base but this will not consider configuration like `basePath` or `locale`:
+
+```typescript
+import type { NextRequest } from 'next/server'
+import { NextResponse } from 'next/server'
+
+export function middleware(request: NextRequest) {
+ return NextResponse.rewrite(new URL('/dest', request.url))
+}
+```
+
+You can also pass directly a string containing a valid absolute URL.
+
+### Useful Links
+
+- [URL Documentation](https://developer.mozilla.org/en-US/docs/Web/API/URL)
+- [Response Documentation](https://developer.mozilla.org/en-US/docs/Web/API/Response)
diff --git a/errors/prerender-error.md b/errors/prerender-error.md
index 75b1c80c1d49a8..8086abafc2da2f 100644
--- a/errors/prerender-error.md
+++ b/errors/prerender-error.md
@@ -10,5 +10,5 @@ While prerendering a page an error occurred. This can occur for many reasons fro
- Check for any code that assumes a prop is available, even when it might not be
- Set default values for all dynamic pages' props (avoid `undefined`, use `null` instead so it can be serialized)
- Check for any out of date modules that you might be relying on
-- Make sure your component handles `fallback` if it is enabled in `getStaticPaths`. [Fallback docs](https://nextjs.org/docs/basic-features/data-fetching#the-fallback-key-required)
+- Make sure your component handles `fallback` if it is enabled in `getStaticPaths`. [Fallback docs](https://nextjs.org/docs/api-reference/data-fetching/get-static-paths#fallback-false)
- Make sure you are not trying to export (`next export`) pages that have server-side rendering enabled [(getServerSideProps)](https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering)
diff --git a/examples/blog-starter/package.json b/examples/blog-starter/package.json
index 1e6a6ae24c82c4..fed5332dfbe155 100644
--- a/examples/blog-starter/package.json
+++ b/examples/blog-starter/package.json
@@ -13,7 +13,7 @@
"react": "^17.0.2",
"react-dom": "^17.0.2",
"remark": "13.0.0",
- "remark-html": "13.0.1"
+ "remark-html": "13.0.2"
},
"devDependencies": {
"autoprefixer": "^10.4.0",
diff --git a/examples/next-forms/.eslintrc.json b/examples/next-forms/.eslintrc.json
new file mode 100644
index 00000000000000..bffb357a712252
--- /dev/null
+++ b/examples/next-forms/.eslintrc.json
@@ -0,0 +1,3 @@
+{
+ "extends": "next/core-web-vitals"
+}
diff --git a/examples/next-forms/.gitignore b/examples/next-forms/.gitignore
new file mode 100644
index 00000000000000..1437c53f70bc21
--- /dev/null
+++ b/examples/next-forms/.gitignore
@@ -0,0 +1,34 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# local env files
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+# vercel
+.vercel
diff --git a/examples/next-forms/README.md b/examples/next-forms/README.md
new file mode 100644
index 00000000000000..2d04b96993d805
--- /dev/null
+++ b/examples/next-forms/README.md
@@ -0,0 +1,27 @@
+# Building Web Forms with Next.js Example
+
+This example shows how you can build forms with Next.js.
+
+## Preview
+
+Preview the example live on [StackBlitz](http://stackblitz.com/):
+
+[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/next-forms)
+
+## Deploy your own
+
+Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
+
+[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/next-forms&project-name=next-forms&repository-name=next-forms)
+
+## How to use
+
+Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
+
+```bash
+npx create-next-app --example next-forms next-forms-app
+# or
+yarn create next-app --example next-forms next-forms-app
+```
+
+Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
diff --git a/examples/next-forms/next.config.js b/examples/next-forms/next.config.js
new file mode 100644
index 00000000000000..0d6071006ab351
--- /dev/null
+++ b/examples/next-forms/next.config.js
@@ -0,0 +1,3 @@
+module.exports = {
+ reactStrictMode: true,
+}
diff --git a/examples/next-forms/package.json b/examples/next-forms/package.json
new file mode 100644
index 00000000000000..9c52e1770b0ff0
--- /dev/null
+++ b/examples/next-forms/package.json
@@ -0,0 +1,18 @@
+{
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint"
+ },
+ "dependencies": {
+ "next": "latest",
+ "react": "17.0.2",
+ "react-dom": "17.0.2"
+ },
+ "devDependencies": {
+ "eslint": "8.4.1",
+ "eslint-config-next": "latest"
+ }
+}
diff --git a/examples/next-forms/pages/_app.js b/examples/next-forms/pages/_app.js
new file mode 100644
index 00000000000000..1e1cec92425c8b
--- /dev/null
+++ b/examples/next-forms/pages/_app.js
@@ -0,0 +1,7 @@
+import '../styles/globals.css'
+
+function MyApp({ Component, pageProps }) {
+ return
+ Get started by looking at{' '}
+ pages/js-form.js
and{' '}
+ pages/no-js-form.js
+
+ Get started by looking at{' '}
+ pages/js-from.js
+
+ Get started by looking at{' '}
+ pages/no-js-from.js
+
dynamic with scoped compound variable
- <_JSXStyle id={"6e7028e37f6f2a1c"} dynamic={[ + <_JSXStyle id={"c37e7ca12a9fbc5b"} dynamic={[ innerProps.color ]}>{`p.__jsx-style-dynamic-selector{color:${innerProps.color}}`} @@ -171,7 +171,7 @@ export const Test9 = ({ color })=>{ }; returndynamic with compound variable
- <_JSXStyle id={"33344a45c20c74fe"} dynamic={[ + <_JSXStyle id={"59d22b990ceca2d7"} dynamic={[ innerProps.color ]}>{`p.__jsx-style-dynamic-selector{color:${innerProps.color}}`}dynamic with constant variable
+dynamic with constant variable
- <_JSXStyle id={"e993bec5c22e1b75"}>{`p.jsx-e993bec5c22e1b75{color:${foo}}`} + <_JSXStyle id={"64eb0b4e09da0dd3"}>{`p.jsx-64eb0b4e09da0dd3{color:${foo}}`}test
+test
- <_JSXStyle id={"f44d626e14f3cfbc"}>{`p.jsx-f44d626e14f3cfbc{color:${color}}`} + <_JSXStyle id={"319ecfcffea32bfb"}>{`p.jsx-319ecfcffea32bfb{color:${color}}`}test
+test
- <_JSXStyle id={"9db1df72abe82640"}>{`p.jsx-9db1df72abe82640{color:${otherColor}}`} + <_JSXStyle id={"8a19b9bd65b986e0"}>{`p.jsx-8a19b9bd65b986e0{color:${otherColor}}`}test
- <_JSXStyle id={"5df43f2861c900e6"}>{`p.${color}.jsx-843124768056a74c{color:${otherColor}; + <_JSXStyle id={"6116059e04f3bff7"}>{`p.${color}.jsx-1ada4ad4dab7822f{color:${otherColor}; display:${obj.display}}`} - <_JSXStyle id={"94239b6d6b42c9b5"}>{"p.jsx-843124768056a74c{color:red}"} + <_JSXStyle id={"94239b6d6b42c9b5"}>{"p.jsx-1ada4ad4dab7822f{color:red}"} - <_JSXStyle id={"a971cf00393d41be"}>{`body{background:${color}}`} + <_JSXStyle id={"171303fb0d7f788b"}>{`body{background:${color}}`} - <_JSXStyle id={"a971cf00393d41be"}>{`body{background:${color}}`} + <_JSXStyle id={"171303fb0d7f788b"}>{`body{background:${color}}`} // TODO: the next two should have the same hash - <_JSXStyle id={"5cadd6714ea141b4"}>{`p.jsx-843124768056a74c{color:${color}}`} + <_JSXStyle id={"332b21af86b0ec6"}>{`p.jsx-1ada4ad4dab7822f{color:${color}}`} - <_JSXStyle id={"5cadd6714ea141b4"}>{`p.jsx-843124768056a74c{color:${color}}`} + <_JSXStyle id={"332b21af86b0ec6"}>{`p.jsx-1ada4ad4dab7822f{color:${color}}`} - <_JSXStyle id={"785cf5e120672da8"} dynamic={[ + <_JSXStyle id={"50021e09364b96c8"} dynamic={[ darken(color) ]}>{`p.__jsx-style-dynamic-selector{color:${darken(color)}}`} - <_JSXStyle id={"108a316873f1c6fc"} dynamic={[ + <_JSXStyle id={"f07deae908c9294f"} dynamic={[ darken(color) + 2 ]}>{`p.__jsx-style-dynamic-selector{color:${darken(color) + 2}}`} - <_JSXStyle id={"bb5a8a5ee5cd36db"}>{`@media (min-width:${mediumScreen}) {p.jsx-843124768056a74c{color:green} -p.jsx-843124768056a74c{color:${`red`}}} -p.jsx-843124768056a74c{color:red}`} + <_JSXStyle id={"4e4be2da62837c76"}>{`@media (min-width:${mediumScreen}) {p.jsx-1ada4ad4dab7822f{color:green} +p.jsx-1ada4ad4dab7822f{color:${`red`}}} +p.jsx-1ada4ad4dab7822f{color:red}`} - <_JSXStyle id={"99746edba785c617"}>{`p.jsx-843124768056a74c{-webkit-animation-duration:${animationDuration}; + <_JSXStyle id={"27040f0829fb73d4"}>{`p.jsx-1ada4ad4dab7822f{-webkit-animation-duration:${animationDuration}; animation-duration:${animationDuration}}`} - <_JSXStyle id={"62d69d091a270e9d"}>{`p.jsx-843124768056a74c{-webkit-animation:${animationDuration} forwards ${animationName}; + <_JSXStyle id={"3e72d735e703a530"}>{`p.jsx-1ada4ad4dab7822f{-webkit-animation:${animationDuration} forwards ${animationName}; animation:${animationDuration} forwards ${animationName}} -div.jsx-843124768056a74c{background:${color}}`} +div.jsx-1ada4ad4dab7822f{background:${color}}`} - <_JSXStyle id={"a124d516c2c0707d"} dynamic={[ + <_JSXStyle id={"183a75aa3877c18a"} dynamic={[ display ? "block" : "none" ]}>{`span.__jsx-style-dynamic-selector{display:${display ? "block" : "none"}}`} diff --git a/packages/next-swc/crates/core/tests/fixture/styled-jsx/issue-30480/output.js b/packages/next-swc/crates/core/tests/fixture/styled-jsx/issue-30480/output.js index 892df0b6ee3b39..d91b5759a13708 100644 --- a/packages/next-swc/crates/core/tests/fixture/styled-jsx/issue-30480/output.js +++ b/packages/next-swc/crates/core/tests/fixture/styled-jsx/issue-30480/output.js @@ -1,14 +1,14 @@ import _JSXStyle from "styled-jsx/style"; export default (({ breakPoint })=>content
"}};var z={element:"span",mutate:function mutate(e){e.setAttribute("style","display: -webkit-flex; display: -ms-flexbox; display: flex;");e.innerHTML='hello'}};var F={element:"form",mutate:function mutate(e){e.setAttribute("tabindex",0);e.setAttribute("disabled","disabled")}};var I={element:"a",mutate:function mutate(e){e.href="#void";e.innerHTML='';return e.querySelector("img")}};var L={element:"div",mutate:function mutate(e){e.innerHTML=''+'';return e.querySelector("img")}};var B={element:function element(e,n){var t=n.createElement("iframe");e.appendChild(t);var r=t.contentWindow.document;r.open();r.close();return t},mutate:function mutate(e){e.style.visibility="hidden";var n=e.contentWindow.document;var t=n.createElement("input");n.body.appendChild(t);return t},validate:function validate(e){var n=e.contentWindow.document;var t=n.querySelector("input");return n.activeElement===t}};var H=!s.is.WEBKIT;function focusInZeroDimensionObject(){return H}var W={element:"div",mutate:function mutate(e){e.setAttribute("tabindex","invalid-value")}};var q={element:"label",mutate:function mutate(e){e.setAttribute("tabindex","-1")},validate:function validate(e,n,t){var r=e.offsetHeight;e.focus();return t.activeElement===e}};var U="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtb"+"G5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBpZD0ic3ZnIj48dGV4dCB4PSIxMCIgeT0iMjAiIGlkPSJ"+"zdmctbGluay10ZXh0Ij50ZXh0PC90ZXh0Pjwvc3ZnPg==";var G={element:"object",mutate:function mutate(e){e.setAttribute("type","image/svg+xml");e.setAttribute("data",U);e.setAttribute("width","200");e.setAttribute("height","50");e.style.visibility="hidden"}};var V={name:"can-focus-object-svg",element:"object",mutate:function mutate(e){e.setAttribute("type","image/svg+xml");e.setAttribute("data",U);e.setAttribute("width","200");e.setAttribute("height","50")},validate:function validate(e,n,t){if(s.is.GECKO){return true}return t.activeElement===e}};var K=!s.is.IE9;function focusObjectSwf(){return K}var Z={element:"div",mutate:function mutate(e){e.innerHTML=''+'';return e.querySelector("img")},validate:function validate(e,n,t){var r=e.querySelector("area");return t.activeElement===r}};var $={element:"fieldset",mutate:function mutate(e){e.innerHTML='';return false},validate:function validate(e,n,t){var r=e.querySelector('input[tabindex="-1"]');var a=e.querySelector('input[tabindex="0"]');e.focus();e.querySelector("legend").focus();return t.activeElement===r&&"focusable"||t.activeElement===a&&"tabbable"||""}};var Y={element:"div",mutate:function mutate(e){e.setAttribute("style","width: 100px; height: 50px; overflow: auto;");e.innerHTML='content
";return e.firstElementChild}};function makeFocusableForeignObject(){var e=document.createElement("div");e.innerHTML='';return e.firstChild.firstChild}function focusSvgForeignObjectHack(e){var n=e.ownerSVGElement||e.nodeName.toLowerCase()==="svg";if(!n){return false}var t=makeFocusableForeignObject();e.appendChild(t);var r=t.querySelector("input");r.focus();r.disabled=true;e.removeChild(t);return true}function generate(e){return'"}function focus(e){if(e.focus){return}try{HTMLElement.prototype.focus.call(e)}catch(n){focusSvgForeignObjectHack(e)}}function validate(e,n,t){focus(n);return t.activeElement===n}var ee={element:"div",mutate:function mutate(e){e.innerHTML=generate('content
"}};var z={element:"span",mutate:function mutate(e){e.setAttribute("style","display: -webkit-flex; display: -ms-flexbox; display: flex;");e.innerHTML='hello'}};var F={element:"form",mutate:function mutate(e){e.setAttribute("tabindex",0);e.setAttribute("disabled","disabled")}};var I={element:"a",mutate:function mutate(e){e.href="#void";e.innerHTML='';return e.querySelector("img")}};var L={element:"div",mutate:function mutate(e){e.innerHTML=''+'';return e.querySelector("img")}};var B={element:function element(e,n){var t=n.createElement("iframe");e.appendChild(t);var r=t.contentWindow.document;r.open();r.close();return t},mutate:function mutate(e){e.style.visibility="hidden";var n=e.contentWindow.document;var t=n.createElement("input");n.body.appendChild(t);return t},validate:function validate(e){var n=e.contentWindow.document;var t=n.querySelector("input");return n.activeElement===t}};var H=!s.is.WEBKIT;function focusInZeroDimensionObject(){return H}var W={element:"div",mutate:function mutate(e){e.setAttribute("tabindex","invalid-value")}};var q={element:"label",mutate:function mutate(e){e.setAttribute("tabindex","-1")},validate:function validate(e,n,t){var r=e.offsetHeight;e.focus();return t.activeElement===e}};var U="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtb"+"G5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBpZD0ic3ZnIj48dGV4dCB4PSIxMCIgeT0iMjAiIGlkPSJ"+"zdmctbGluay10ZXh0Ij50ZXh0PC90ZXh0Pjwvc3ZnPg==";var G={element:"object",mutate:function mutate(e){e.setAttribute("type","image/svg+xml");e.setAttribute("data",U);e.setAttribute("width","200");e.setAttribute("height","50");e.style.visibility="hidden"}};var V={name:"can-focus-object-svg",element:"object",mutate:function mutate(e){e.setAttribute("type","image/svg+xml");e.setAttribute("data",U);e.setAttribute("width","200");e.setAttribute("height","50")},validate:function validate(e,n,t){if(s.is.GECKO){return true}return t.activeElement===e}};var K=!s.is.IE9;function focusObjectSwf(){return K}var Z={element:"div",mutate:function mutate(e){e.innerHTML=''+'';return e.querySelector("img")},validate:function validate(e,n,t){var r=e.querySelector("area");return t.activeElement===r}};var $={element:"fieldset",mutate:function mutate(e){e.innerHTML='';return false},validate:function validate(e,n,t){var r=e.querySelector('input[tabindex="-1"]');var a=e.querySelector('input[tabindex="0"]');e.focus();e.querySelector("legend").focus();return t.activeElement===r&&"focusable"||t.activeElement===a&&"tabbable"||""}};var Y={element:"div",mutate:function mutate(e){e.setAttribute("style","width: 100px; height: 50px; overflow: auto;");e.innerHTML='content
";return e.firstElementChild}};function makeFocusableForeignObject(){var e=document.createElement("div");e.innerHTML='';return e.firstChild.firstChild}function focusSvgForeignObjectHack(e){var n=e.ownerSVGElement||e.nodeName.toLowerCase()==="svg";if(!n){return false}var t=makeFocusableForeignObject();e.appendChild(t);var r=t.querySelector("input");r.focus();r.disabled=true;e.removeChild(t);return true}function generate(e){return'"}function focus(e){if(e.focus){return}try{HTMLElement.prototype.focus.call(e)}catch(n){focusSvgForeignObjectHack(e)}}function validate(e,n,t){focus(n);return t.activeElement===n}var ee={element:"div",mutate:function mutate(e){e.innerHTML=generate('You can find more information in the related Webpack error below:
+You can find more information in the related error below:
early request end
+} diff --git a/test/integration/getserversideprops/pages/enoent.js b/test/e2e/getserversideprops/app/pages/enoent.js similarity index 100% rename from test/integration/getserversideprops/pages/enoent.js rename to test/e2e/getserversideprops/app/pages/enoent.js diff --git a/test/integration/getserversideprops/pages/index.js b/test/e2e/getserversideprops/app/pages/index.js similarity index 100% rename from test/integration/getserversideprops/pages/index.js rename to test/e2e/getserversideprops/app/pages/index.js diff --git a/test/integration/getserversideprops/pages/invalid-keys.js b/test/e2e/getserversideprops/app/pages/invalid-keys.js similarity index 100% rename from test/integration/getserversideprops/pages/invalid-keys.js rename to test/e2e/getserversideprops/app/pages/invalid-keys.js diff --git a/test/integration/getserversideprops/pages/non-json.js b/test/e2e/getserversideprops/app/pages/non-json.js similarity index 100% rename from test/integration/getserversideprops/pages/non-json.js rename to test/e2e/getserversideprops/app/pages/non-json.js diff --git a/test/integration/getserversideprops/pages/normal.js b/test/e2e/getserversideprops/app/pages/normal.js similarity index 100% rename from test/integration/getserversideprops/pages/normal.js rename to test/e2e/getserversideprops/app/pages/normal.js diff --git a/test/integration/getserversideprops/pages/not-found/[slug].js b/test/e2e/getserversideprops/app/pages/not-found/[slug].js similarity index 100% rename from test/integration/getserversideprops/pages/not-found/[slug].js rename to test/e2e/getserversideprops/app/pages/not-found/[slug].js diff --git a/test/integration/getserversideprops/pages/not-found/index.js b/test/e2e/getserversideprops/app/pages/not-found/index.js similarity index 100% rename from test/integration/getserversideprops/pages/not-found/index.js rename to test/e2e/getserversideprops/app/pages/not-found/index.js diff --git a/test/integration/getserversideprops/pages/promise/index.js b/test/e2e/getserversideprops/app/pages/promise/index.js similarity index 100% rename from test/integration/getserversideprops/pages/promise/index.js rename to test/e2e/getserversideprops/app/pages/promise/index.js diff --git a/test/integration/getserversideprops/pages/promise/mutate-res-no-streaming.js b/test/e2e/getserversideprops/app/pages/promise/mutate-res-no-streaming.js similarity index 100% rename from test/integration/getserversideprops/pages/promise/mutate-res-no-streaming.js rename to test/e2e/getserversideprops/app/pages/promise/mutate-res-no-streaming.js diff --git a/test/integration/getserversideprops/pages/promise/mutate-res-props.js b/test/e2e/getserversideprops/app/pages/promise/mutate-res-props.js similarity index 100% rename from test/integration/getserversideprops/pages/promise/mutate-res-props.js rename to test/e2e/getserversideprops/app/pages/promise/mutate-res-props.js diff --git a/test/integration/getserversideprops/pages/promise/mutate-res.js b/test/e2e/getserversideprops/app/pages/promise/mutate-res.js similarity index 100% rename from test/integration/getserversideprops/pages/promise/mutate-res.js rename to test/e2e/getserversideprops/app/pages/promise/mutate-res.js diff --git a/test/integration/getserversideprops/pages/refresh.js b/test/e2e/getserversideprops/app/pages/refresh.js similarity index 100% rename from test/integration/getserversideprops/pages/refresh.js rename to test/e2e/getserversideprops/app/pages/refresh.js diff --git a/test/integration/getserversideprops/pages/slow/index.js b/test/e2e/getserversideprops/app/pages/slow/index.js similarity index 100% rename from test/integration/getserversideprops/pages/slow/index.js rename to test/e2e/getserversideprops/app/pages/slow/index.js diff --git a/test/integration/getserversideprops/pages/something.js b/test/e2e/getserversideprops/app/pages/something.js similarity index 100% rename from test/integration/getserversideprops/pages/something.js rename to test/e2e/getserversideprops/app/pages/something.js diff --git a/test/integration/getserversideprops/pages/user/[user]/profile.js b/test/e2e/getserversideprops/app/pages/user/[user]/profile.js similarity index 100% rename from test/integration/getserversideprops/pages/user/[user]/profile.js rename to test/e2e/getserversideprops/app/pages/user/[user]/profile.js diff --git a/test/integration/getserversideprops/world.txt b/test/e2e/getserversideprops/app/world.txt similarity index 100% rename from test/integration/getserversideprops/world.txt rename to test/e2e/getserversideprops/app/world.txt diff --git a/test/integration/getserversideprops/test/index.test.js b/test/e2e/getserversideprops/test/index.test.ts similarity index 81% rename from test/integration/getserversideprops/test/index.test.js rename to test/e2e/getserversideprops/test/index.test.ts index 99bfa6c8484cce..fc425ddcff7039 100644 --- a/test/integration/getserversideprops/test/index.test.js +++ b/test/e2e/getserversideprops/test/index.test.ts @@ -1,33 +1,25 @@ /* eslint-env jest */ import cheerio from 'cheerio' +import { createNext, FileRef } from 'e2e-utils' import escapeRegex from 'escape-string-regexp' -import fs from 'fs-extra' import { check, fetchViaHTTP, - findPort, - File, getBrowserBodyText, getRedboxHeader, - killApp, - launchApp, - nextBuild, - nextStart, normalizeRegEx, renderViaHTTP, waitFor, } from 'next-test-utils' -import webdriver from 'next-webdriver' import { join } from 'path' +import webdriver from 'next-webdriver' +import { NextInstance } from 'test/lib/next-modes/base' -const appDir = join(__dirname, '..') -const nextConfig = new File(join(appDir, 'next.config.js')) +const appDir = join(__dirname, '../app') -let app -let appPort let buildId -let stderr +let next: NextInstance const expectedManifestRoutes = () => [ { @@ -99,6 +91,12 @@ const expectedManifestRoutes = () => [ ), page: '/default-revalidate', }, + { + dataRouteRegex: `^\\/_next\\/data\\/${escapeRegex( + buildId + )}\\/early-request-end.json$`, + page: '/early-request-end', + }, { dataRouteRegex: normalizeRegEx( `^\\/_next\\/data\\/${escapeRegex(buildId)}\\/enoent.json$` @@ -197,7 +195,7 @@ const expectedManifestRoutes = () => [ }, ] -const navigateTest = (dev = false) => { +const navigateTest = () => { it('should navigate between pages successfully', async () => { const toBuild = [ '/', @@ -208,9 +206,9 @@ const navigateTest = (dev = false) => { '/blog/post-1/comment-1', ] - await Promise.all(toBuild.map((pg) => renderViaHTTP(appPort, pg))) + await Promise.all(toBuild.map((pg) => renderViaHTTP(next.url, pg))) - const browser = await webdriver(appPort, '/') + const browser = await webdriver(next.url, '/') let text = await browser.elementByCss('p').text() expect(text).toMatch(/hello.*?world/) @@ -284,23 +282,29 @@ const navigateTest = (dev = false) => { } const runTests = (dev = false) => { - navigateTest(dev) + navigateTest() + + it('should work with early request ending', async () => { + const res = await fetchViaHTTP(next.url, '/early-request-end') + expect(res.status).toBe(200) + expect(await res.text()).toBe('hello from gssp') + }) it('should render correctly when notFound is false (non-dynamic)', async () => { - const res = await fetchViaHTTP(appPort, '/not-found') + const res = await fetchViaHTTP(next.url, '/not-found') expect(res.status).toBe(200) }) it('should render 404 correctly when notFound is returned (non-dynamic)', async () => { - const res = await fetchViaHTTP(appPort, '/not-found', { hiding: true }) + const res = await fetchViaHTTP(next.url, '/not-found', { hiding: true }) expect(res.status).toBe(404) expect(await res.text()).toContain('This page could not be found') }) it('should render 404 correctly when notFound is returned client-transition (non-dynamic)', async () => { - const browser = await webdriver(appPort, '/') + const browser = await webdriver(next.url, '/') await browser.eval(`(function() { window.beforeNav = 1 window.next.router.push('/not-found?hiding=true') @@ -314,13 +318,13 @@ const runTests = (dev = false) => { }) it('should render correctly when notFound is false (dynamic)', async () => { - const res = await fetchViaHTTP(appPort, '/not-found/first') + const res = await fetchViaHTTP(next.url, '/not-found/first') expect(res.status).toBe(200) }) it('should render 404 correctly when notFound is returned (dynamic)', async () => { - const res = await fetchViaHTTP(appPort, '/not-found/first', { + const res = await fetchViaHTTP(next.url, '/not-found/first', { hiding: true, }) @@ -329,7 +333,7 @@ const runTests = (dev = false) => { }) it('should render 404 correctly when notFound is returned client-transition (dynamic)', async () => { - const browser = await webdriver(appPort, '/') + const browser = await webdriver(next.url, '/') await browser.eval(`(function() { window.beforeNav = 1 window.next.router.push('/not-found/first?hiding=true') @@ -343,17 +347,17 @@ const runTests = (dev = false) => { }) it('should SSR normal page correctly', async () => { - const html = await renderViaHTTP(appPort, '/') + const html = await renderViaHTTP(next.url, '/') expect(html).toMatch(/hello.*?world/) }) it('should SSR getServerSideProps page correctly', async () => { - const html = await renderViaHTTP(appPort, '/blog/post-1') + const html = await renderViaHTTP(next.url, '/blog/post-1') expect(html).toMatch(/Post:.*?post-1/) }) it('should handle throw ENOENT correctly', async () => { - const res = await fetchViaHTTP(appPort, '/enoent') + const res = await fetchViaHTTP(next.url, '/enoent') const html = await res.text() if (dev) { @@ -366,19 +370,19 @@ const runTests = (dev = false) => { }) it('should have gssp in __NEXT_DATA__', async () => { - const html = await renderViaHTTP(appPort, '/') + const html = await renderViaHTTP(next.url, '/') const $ = cheerio.load(html) expect(JSON.parse($('#__NEXT_DATA__').text()).gssp).toBe(true) }) it('should not have gssp in __NEXT_DATA__ for non-GSSP page', async () => { - const html = await renderViaHTTP(appPort, '/normal') + const html = await renderViaHTTP(next.url, '/normal') const $ = cheerio.load(html) expect('gssp' in JSON.parse($('#__NEXT_DATA__').text())).toBe(false) }) it('should supply query values SSR', async () => { - const html = await renderViaHTTP(appPort, '/blog/post-1?hello=world') + const html = await renderViaHTTP(next.url, '/blog/post-1?hello=world') const $ = cheerio.load(html) const params = $('#params').text() expect(JSON.parse(params)).toEqual({ post: 'post-1' }) @@ -387,7 +391,7 @@ const runTests = (dev = false) => { }) it('should supply params values for catchall correctly', async () => { - const html = await renderViaHTTP(appPort, '/catchall/first') + const html = await renderViaHTTP(next.url, '/catchall/first') const $ = cheerio.load(html) const params = $('#params').text() expect(JSON.parse(params)).toEqual({ path: ['first'] }) @@ -395,7 +399,10 @@ const runTests = (dev = false) => { expect(JSON.parse(query)).toEqual({ path: ['first'] }) const data = JSON.parse( - await renderViaHTTP(appPort, `/_next/data/${buildId}/catchall/first.json`) + await renderViaHTTP( + next.url, + `/_next/data/${buildId}/catchall/first.json` + ) ) expect(data.pageProps.params).toEqual({ path: ['first'] }) @@ -403,7 +410,7 @@ const runTests = (dev = false) => { it('should have original req.url for /_next/data request dynamic page', async () => { const curUrl = `/_next/data/${buildId}/blog/post-1.json` - const data = await renderViaHTTP(appPort, curUrl) + const data = await renderViaHTTP(next.url, curUrl) const { appProps, pageProps } = JSON.parse(data) expect(pageProps.resolvedUrl).toEqual('/blog/post-1') @@ -418,7 +425,7 @@ const runTests = (dev = false) => { it('should have original req.url for /_next/data request dynamic page with query', async () => { const curUrl = `/_next/data/${buildId}/blog/post-1.json` - const data = await renderViaHTTP(appPort, curUrl, { hello: 'world' }) + const data = await renderViaHTTP(next.url, curUrl, { hello: 'world' }) const { appProps, pageProps } = JSON.parse(data) expect(pageProps.resolvedUrl).toEqual('/blog/post-1?hello=world') @@ -433,7 +440,7 @@ const runTests = (dev = false) => { it('should have original req.url for /_next/data request', async () => { const curUrl = `/_next/data/${buildId}/something.json` - const data = await renderViaHTTP(appPort, curUrl) + const data = await renderViaHTTP(next.url, curUrl) const { appProps, pageProps } = JSON.parse(data) expect(pageProps.resolvedUrl).toEqual('/something') @@ -448,7 +455,7 @@ const runTests = (dev = false) => { it('should have original req.url for /_next/data request with query', async () => { const curUrl = `/_next/data/${buildId}/something.json` - const data = await renderViaHTTP(appPort, curUrl, { hello: 'world' }) + const data = await renderViaHTTP(next.url, curUrl, { hello: 'world' }) const { appProps, pageProps } = JSON.parse(data) expect(pageProps.resolvedUrl).toEqual('/something?hello=world') @@ -462,7 +469,7 @@ const runTests = (dev = false) => { }) it('should have correct req.url and query for direct visit dynamic page', async () => { - const html = await renderViaHTTP(appPort, '/blog/post-1') + const html = await renderViaHTTP(next.url, '/blog/post-1') const $ = cheerio.load(html) expect($('#app-url').text()).toContain('/blog/post-1') expect(JSON.parse($('#app-query').text())).toEqual({ post: 'post-1' }) @@ -471,7 +478,7 @@ const runTests = (dev = false) => { }) it('should have correct req.url and query for direct visit dynamic page rewrite direct', async () => { - const html = await renderViaHTTP(appPort, '/blog-post-1') + const html = await renderViaHTTP(next.url, '/blog-post-1') const $ = cheerio.load(html) expect($('#app-url').text()).toContain('/blog-post-1') expect(JSON.parse($('#app-query').text())).toEqual({ post: 'post-1' }) @@ -480,7 +487,7 @@ const runTests = (dev = false) => { }) it('should have correct req.url and query for direct visit dynamic page rewrite direct with internal query', async () => { - const html = await renderViaHTTP(appPort, '/blog-post-2') + const html = await renderViaHTTP(next.url, '/blog-post-2') const $ = cheerio.load(html) expect($('#app-url').text()).toContain('/blog-post-2') expect(JSON.parse($('#app-query').text())).toEqual({ @@ -492,7 +499,7 @@ const runTests = (dev = false) => { }) it('should have correct req.url and query for direct visit dynamic page rewrite param', async () => { - const html = await renderViaHTTP(appPort, '/blog-post-3') + const html = await renderViaHTTP(next.url, '/blog-post-3') const $ = cheerio.load(html) expect($('#app-url').text()).toContain('/blog-post-3') expect(JSON.parse($('#app-query').text())).toEqual({ @@ -504,7 +511,7 @@ const runTests = (dev = false) => { }) it('should have correct req.url and query for direct visit dynamic page with query', async () => { - const html = await renderViaHTTP(appPort, '/blog/post-1', { + const html = await renderViaHTTP(next.url, '/blog/post-1', { hello: 'world', }) const $ = cheerio.load(html) @@ -518,7 +525,7 @@ const runTests = (dev = false) => { }) it('should have correct req.url and query for direct visit', async () => { - const html = await renderViaHTTP(appPort, '/something') + const html = await renderViaHTTP(next.url, '/something') const $ = cheerio.load(html) expect($('#app-url').text()).toContain('/something') expect(JSON.parse($('#app-query').text())).toEqual({}) @@ -528,7 +535,7 @@ const runTests = (dev = false) => { it('should return data correctly', async () => { const data = JSON.parse( - await renderViaHTTP(appPort, `/_next/data/${buildId}/something.json`) + await renderViaHTTP(next.url, `/_next/data/${buildId}/something.json`) ) expect(data.pageProps.world).toBe('world') }) @@ -536,7 +543,7 @@ const runTests = (dev = false) => { it('should pass query for data request', async () => { const data = JSON.parse( await renderViaHTTP( - appPort, + next.url, `/_next/data/${buildId}/something.json?another=thing` ) ) @@ -545,18 +552,18 @@ const runTests = (dev = false) => { it('should return data correctly for dynamic page', async () => { const data = JSON.parse( - await renderViaHTTP(appPort, `/_next/data/${buildId}/blog/post-1.json`) + await renderViaHTTP(next.url, `/_next/data/${buildId}/blog/post-1.json`) ) expect(data.pageProps.post).toBe('post-1') }) it('should return data correctly when props is a promise', async () => { - const html = await renderViaHTTP(appPort, `/promise`) + const html = await renderViaHTTP(next.url, `/promise`) expect(html).toMatch(/hello.*?promise/) }) it('should navigate to a normal page and back', async () => { - const browser = await webdriver(appPort, '/') + const browser = await webdriver(next.url, '/') let text = await browser.elementByCss('p').text() expect(text).toMatch(/hello.*?world/) @@ -567,7 +574,7 @@ const runTests = (dev = false) => { }) it('should load a fast refresh page', async () => { - const browser = await webdriver(appPort, '/refresh') + const browser = await webdriver(next.url, '/refresh') expect( await check( () => browser.elementByCss('p').text(), @@ -579,7 +586,7 @@ const runTests = (dev = false) => { it('should provide correct query value for dynamic page', async () => { const html = await renderViaHTTP( - appPort, + next.url, '/blog/post-1?post=something-else' ) const $ = cheerio.load(html) @@ -588,7 +595,7 @@ const runTests = (dev = false) => { }) it('should parse query values on mount correctly', async () => { - const browser = await webdriver(appPort, '/blog/post-1?another=value') + const browser = await webdriver(next.url, '/blog/post-1?another=value') await waitFor(2000) const text = await browser.elementByCss('#query').text() expect(text).toMatch(/another.*?value/) @@ -596,7 +603,7 @@ const runTests = (dev = false) => { }) it('should pass query for data request on navigation', async () => { - const browser = await webdriver(appPort, '/') + const browser = await webdriver(next.url, '/') await browser.eval('window.beforeNav = true') await browser.elementByCss('#something-query').click() await browser.waitForElementByCss('#initial-query') @@ -608,7 +615,7 @@ const runTests = (dev = false) => { }) it('should reload page on failed data request', async () => { - const browser = await webdriver(appPort, '/') + const browser = await webdriver(next.url, '/') await waitFor(500) await browser.eval('window.beforeClick = "abc"') await browser.elementByCss('#broken-post').click() @@ -622,23 +629,23 @@ const runTests = (dev = false) => { }) it('should always call getServerSideProps without caching', async () => { - const initialRes = await fetchViaHTTP(appPort, '/something') + const initialRes = await fetchViaHTTP(next.url, '/something') const initialHtml = await initialRes.text() expect(initialHtml).toMatch(/hello.*?world/) - const newRes = await fetchViaHTTP(appPort, '/something') + const newRes = await fetchViaHTTP(next.url, '/something') const newHtml = await newRes.text() expect(newHtml).toMatch(/hello.*?world/) expect(initialHtml !== newHtml).toBe(true) - const newerRes = await fetchViaHTTP(appPort, '/something') + const newerRes = await fetchViaHTTP(next.url, '/something') const newerHtml = await newerRes.text() expect(newerHtml).toMatch(/hello.*?world/) expect(newHtml !== newerHtml).toBe(true) }) it('should not re-call getServerSideProps when updating query', async () => { - const browser = await webdriver(appPort, '/something?hello=world') + const browser = await webdriver(next.url, '/something?hello=world') await waitFor(2000) const query = await browser.elementByCss('#query').text() @@ -655,7 +662,7 @@ const runTests = (dev = false) => { }) it('should dedupe server data requests', async () => { - const browser = await webdriver(appPort, '/') + const browser = await webdriver(next.url, '/') await waitFor(2000) // Keep clicking on the link @@ -682,8 +689,8 @@ const runTests = (dev = false) => { if (dev) { it('should not show warning from url prop being returned', async () => { - const urlPropPage = join(appDir, 'pages/url-prop.js') - await fs.writeFile( + const urlPropPage = 'pages/url-prop.js' + await next.patchFile( urlPropPage, ` export async function getServerSideProps() { @@ -698,16 +705,16 @@ const runTests = (dev = false) => { ` ) - const html = await renderViaHTTP(appPort, '/url-prop') - await fs.remove(urlPropPage) - expect(stderr).not.toMatch( + const html = await renderViaHTTP(next.url, '/url-prop') + await next.deleteFile(urlPropPage) + expect(next.cliOutput).not.toMatch( /The prop `url` is a reserved prop in Next.js for legacy reasons and will be overridden on page \/url-prop/ ) expect(html).toMatch(/url:.*?something/) }) it('should show error for extra keys returned from getServerSideProps', async () => { - const html = await renderViaHTTP(appPort, '/invalid-keys') + const html = await renderViaHTTP(next.url, '/invalid-keys') expect(html).toContain( `Additional keys were returned from \`getServerSideProps\`. Properties intended for your component must be nested under the \`props\` key, e.g.:` ) @@ -717,14 +724,14 @@ const runTests = (dev = false) => { }) it('should show error for invalid JSON returned from getServerSideProps', async () => { - const html = await renderViaHTTP(appPort, '/non-json') + const html = await renderViaHTTP(next.url, '/non-json') expect(html).toContain( 'Error serializing `.time` returned from `getServerSideProps`' ) }) it('should show error for invalid JSON returned from getStaticProps on CST', async () => { - const browser = await webdriver(appPort, '/') + const browser = await webdriver(next.url, '/') await browser.elementByCss('#non-json').click() await check( @@ -734,14 +741,14 @@ const runTests = (dev = false) => { }) it('should show error for accessing res after gssp returns', async () => { - const html = await renderViaHTTP(appPort, '/promise/mutate-res') + const html = await renderViaHTTP(next.url, '/promise/mutate-res') expect(html).toContain( `You should not access 'res' after getServerSideProps resolves` ) }) it('should show error for accessing res through props promise after gssp returns', async () => { - const html = await renderViaHTTP(appPort, '/promise/mutate-res-props') + const html = await renderViaHTTP(next.url, '/promise/mutate-res-props') expect(html).toContain( `You should not access 'res' after getServerSideProps resolves` ) @@ -749,19 +756,19 @@ const runTests = (dev = false) => { it('should only warn for accessing res if not streaming', async () => { const html = await renderViaHTTP( - appPort, + next.url, '/promise/mutate-res-no-streaming' ) expect(html).not.toContain( `You should not access 'res' after getServerSideProps resolves` ) - expect(stderr).toContain( + expect(next.cliOutput).toContain( `You should not access 'res' after getServerSideProps resolves` ) }) } else { it('should not fetch data on mount', async () => { - const browser = await webdriver(appPort, '/blog/post-100') + const browser = await webdriver(next.url, '/blog/post-100') await browser.eval('window.thisShouldStay = true') await waitFor(2 * 1000) const val = await browser.eval('window.thisShouldStay') @@ -769,8 +776,8 @@ const runTests = (dev = false) => { }) it('should output routes-manifest correctly', async () => { - const { dataRoutes } = await fs.readJSON( - join(appDir, '.next/routes-manifest.json') + const { dataRoutes } = JSON.parse( + await next.readFile('.next/routes-manifest.json') ) for (const route of dataRoutes) { route.dataRouteRegex = normalizeRegEx(route.dataRouteRegex) @@ -780,13 +787,13 @@ const runTests = (dev = false) => { }) it('should set default caching header', async () => { - const resPage = await fetchViaHTTP(appPort, `/something`) + const resPage = await fetchViaHTTP(next.url, `/something`) expect(resPage.headers.get('cache-control')).toBe( 'private, no-cache, no-store, max-age=0, must-revalidate' ) const resData = await fetchViaHTTP( - appPort, + next.url, `/_next/data/${buildId}/something.json` ) expect(resData.headers.get('cache-control')).toBe( @@ -795,92 +802,52 @@ const runTests = (dev = false) => { }) it('should respect custom caching header', async () => { - const resPage = await fetchViaHTTP(appPort, `/custom-cache`) + const resPage = await fetchViaHTTP(next.url, `/custom-cache`) expect(resPage.headers.get('cache-control')).toBe('public, max-age=3600') const resData = await fetchViaHTTP( - appPort, + next.url, `/_next/data/${buildId}/custom-cache.json` ) expect(resData.headers.get('cache-control')).toBe('public, max-age=3600') }) it('should not show error for invalid JSON returned from getServerSideProps', async () => { - const html = await renderViaHTTP(appPort, '/non-json') + const html = await renderViaHTTP(next.url, '/non-json') expect(html).not.toContain('Error serializing') expect(html).toContain('hello ') }) it('should not show error for invalid JSON returned from getStaticProps on CST', async () => { - const browser = await webdriver(appPort, '/') + const browser = await webdriver(next.url, '/') await browser.elementByCss('#non-json').click() await check(() => getBrowserBodyText(browser), /hello /) }) it('should not show error for accessing res after gssp returns', async () => { - const html = await renderViaHTTP(appPort, '/promise/mutate-res') + const html = await renderViaHTTP(next.url, '/promise/mutate-res') expect(html).toMatch(/hello.*?res/) }) it('should not warn for accessing res after gssp returns', async () => { - const html = await renderViaHTTP(appPort, '/promise/mutate-res') + const html = await renderViaHTTP(next.url, '/promise/mutate-res') expect(html).toMatch(/hello.*?res/) }) } } describe('getServerSideProps', () => { - describe('dev mode', () => { - beforeAll(async () => { - stderr = '' - appPort = await findPort() - app = await launchApp(appDir, appPort, { - env: { __NEXT_TEST_WITH_DEVTOOL: 1 }, - onStderr(msg) { - stderr += msg - }, - }) - buildId = 'development' - }) - afterAll(() => killApp(app)) - - runTests(true) - }) - - describe('serverless mode', () => { - beforeAll(async () => { - await nextConfig.replace( - '// replace me', - `target: 'experimental-serverless-trace', ` - ) - await nextBuild(appDir) - stderr = '' - appPort = await findPort() - app = await nextStart(appDir, appPort, { - onStderr(msg) { - stderr += msg - }, - }) - buildId = await fs.readFile(join(appDir, '.next/BUILD_ID'), 'utf8') - }) - afterAll(async () => { - await killApp(app) - nextConfig.restore() + beforeAll(async () => { + next = await createNext({ + files: { + pages: new FileRef(join(appDir, 'pages')), + 'world.txt': new FileRef(join(appDir, 'world.txt')), + 'next.config.js': new FileRef(join(appDir, 'next.config.js')), + }, }) - - runTests() + buildId = next.buildId }) + afterAll(() => next.destroy()) - describe('production mode', () => { - beforeAll(async () => { - await nextBuild(appDir, [], { stdout: true }) - - appPort = await findPort() - app = await nextStart(appDir, appPort) - buildId = await fs.readFile(join(appDir, '.next/BUILD_ID'), 'utf8') - }) - afterAll(() => killApp(app)) - - runTests() - }) + runTests((global as any).isNextDev) }) diff --git a/test/e2e/yarn-pnp/test/pwa-example.test.ts b/test/e2e/yarn-pnp/test/pwa-example.test.ts new file mode 100644 index 00000000000000..03c6e38de04738 --- /dev/null +++ b/test/e2e/yarn-pnp/test/pwa-example.test.ts @@ -0,0 +1,5 @@ +import { runTests } from './utils' + +describe('yarn PnP', () => { + runTests('progressive-web-app') +}) diff --git a/test/e2e/yarn-pnp/test/utils.ts b/test/e2e/yarn-pnp/test/utils.ts new file mode 100644 index 00000000000000..d7b3f89d810b8f --- /dev/null +++ b/test/e2e/yarn-pnp/test/utils.ts @@ -0,0 +1,49 @@ +import fs from 'fs-extra' +import { join } from 'path' +import { fetchViaHTTP } from 'next-test-utils' +import { createNext, FileRef } from 'e2e-utils' +import { NextInstance } from 'test/lib/next-modes/base' + +jest.setTimeout(2 * 60 * 1000) + +export function runTests(example = '') { + let next: NextInstance + + beforeAll(async () => { + const srcDir = join(__dirname, '../../../../examples', example) + const srcFiles = await fs.readdir(srcDir) + + const packageJson = await fs.readJson(join(srcDir, 'package.json')) + + next = await createNext({ + files: srcFiles.reduce((prev, file) => { + if (file !== 'package.json') { + prev[file] = new FileRef(join(srcDir, file)) + } + return prev + }, {} as { [key: string]: FileRef }), + dependencies: { + ...packageJson.dependencies, + ...packageJson.devDependencies, + }, + installCommand: ({ dependencies }) => { + const pkgs = Object.keys(dependencies).reduce((prev, cur) => { + prev.push(`${cur}@${dependencies[cur]}`) + return prev + }, [] as string[]) + return `yarn set version berry && yarn config set enableGlobalCache true && yarn config set compressionLevel 0 && yarn add ${pkgs.join( + ' ' + )}` + }, + buildCommand: `yarn next build --no-lint`, + startCommand: (global as any).isNextDev ? `yarn next` : `yarn next start`, + }) + }) + afterAll(() => next?.destroy()) + + it(`should compile and serve the index page correctly ${example}`, async () => { + const res = await fetchViaHTTP(next.url, '/') + expect(res.status).toBe(200) + expect(await res.text()).toContain(' { + runTests('with-eslint') +}) diff --git a/test/e2e/yarn-pnp/test/with-mdx.test.ts b/test/e2e/yarn-pnp/test/with-mdx.test.ts new file mode 100644 index 00000000000000..e25d3400037b07 --- /dev/null +++ b/test/e2e/yarn-pnp/test/with-mdx.test.ts @@ -0,0 +1,5 @@ +import { runTests } from './utils' + +describe('yarn PnP', () => { + runTests('with-mdx') +}) diff --git a/test/e2e/yarn-pnp/test/with-next-sass.test.ts b/test/e2e/yarn-pnp/test/with-next-sass.test.ts new file mode 100644 index 00000000000000..d1e5319da15794 --- /dev/null +++ b/test/e2e/yarn-pnp/test/with-next-sass.test.ts @@ -0,0 +1,5 @@ +import { runTests } from './utils' + +describe('yarn PnP', () => { + runTests('with-next-sass') +}) diff --git a/test/e2e/yarn-pnp/test/with-styled-components.test.ts b/test/e2e/yarn-pnp/test/with-styled-components.test.ts new file mode 100644 index 00000000000000..f8ee921afe9885 --- /dev/null +++ b/test/e2e/yarn-pnp/test/with-styled-components.test.ts @@ -0,0 +1,5 @@ +import { runTests } from './utils' + +describe('yarn PnP', () => { + runTests('with-styled-components') +}) diff --git a/test/e2e/yarn-pnp/test/with-typescript.test.ts b/test/e2e/yarn-pnp/test/with-typescript.test.ts new file mode 100644 index 00000000000000..07a11a31cea7a8 --- /dev/null +++ b/test/e2e/yarn-pnp/test/with-typescript.test.ts @@ -0,0 +1,5 @@ +import { runTests } from './utils' + +describe('yarn PnP', () => { + runTests('with-typescript') +}) diff --git a/test/integration/export-fallback-true-error/next.config.js b/test/integration/export-fallback-true-error/next.config.js new file mode 100644 index 00000000000000..b305e3b292401f --- /dev/null +++ b/test/integration/export-fallback-true-error/next.config.js @@ -0,0 +1,7 @@ +module.exports = { + exportPathMap() { + return { + '/first': { page: '/[slug]' }, + } + }, +} diff --git a/test/integration/middleware/core/pages/interface/_middleware.js b/test/integration/middleware/core/pages/interface/_middleware.js index 81b57cba1fb4db..1aee8917ed5c98 100644 --- a/test/integration/middleware/core/pages/interface/_middleware.js +++ b/test/integration/middleware/core/pages/interface/_middleware.js @@ -81,7 +81,8 @@ export async function middleware(request) { } if (url.pathname.endsWith('/dynamic-replace')) { - return NextResponse.rewrite('/_interface/dynamic-path') + url.pathname = '/_interface/dynamic-path' + return NextResponse.rewrite(url) } return new Response(null, { diff --git a/test/integration/middleware/core/pages/redirects/_middleware.js b/test/integration/middleware/core/pages/redirects/_middleware.js index e97cb99011b04a..0a8dafc5c8b262 100644 --- a/test/integration/middleware/core/pages/redirects/_middleware.js +++ b/test/integration/middleware/core/pages/redirects/_middleware.js @@ -56,6 +56,6 @@ export async function middleware(request) { if (url.pathname === '/redirects/infinite-loop-1') { url.pathname = '/redirects/infinite-loop' - return Response.redirect(url.pathname) + return Response.redirect(url) } } diff --git a/test/integration/middleware/core/pages/rewrites/_middleware.js b/test/integration/middleware/core/pages/rewrites/_middleware.js index 13d44539382518..20113e2978d161 100644 --- a/test/integration/middleware/core/pages/rewrites/_middleware.js +++ b/test/integration/middleware/core/pages/rewrites/_middleware.js @@ -5,24 +5,27 @@ export async function middleware(request) { if (url.pathname.startsWith('/rewrites/to-blog')) { const slug = url.pathname.split('/').pop() - console.log('rewriting to slug', slug) - return NextResponse.rewrite(`/rewrites/fallback-true-blog/${slug}`) + url.pathname = `/rewrites/fallback-true-blog/${slug}` + return NextResponse.rewrite(url) } if (url.pathname === '/rewrites/rewrite-to-ab-test') { let bucket = request.cookies.bucket if (!bucket) { bucket = Math.random() >= 0.5 ? 'a' : 'b' - const response = NextResponse.rewrite(`/rewrites/${bucket}`) + url.pathname = `/rewrites/${bucket}` + const response = NextResponse.rewrite(url) response.cookie('bucket', bucket, { maxAge: 10000 }) return response } - return NextResponse.rewrite(`/rewrites/${bucket}`) + url.pathname = `/rewrites/${bucket}` + return NextResponse.rewrite(url) } if (url.pathname === '/rewrites/rewrite-me-to-about') { - return NextResponse.rewrite('/rewrites/about') + url.pathname = '/rewrites/about' + return NextResponse.rewrite(url) } if (url.pathname === '/rewrites/rewrite-me-to-vercel') { diff --git a/test/integration/middleware/core/pages/urls/_middleware.js b/test/integration/middleware/core/pages/urls/_middleware.js new file mode 100644 index 00000000000000..43efc371d2e6dd --- /dev/null +++ b/test/integration/middleware/core/pages/urls/_middleware.js @@ -0,0 +1,35 @@ +import { NextResponse, NextRequest } from 'next/server' + +export function middleware(request) { + try { + if (request.nextUrl.pathname === '/urls/relative-url') { + return NextResponse.json({ message: String(new URL('/relative')) }) + } + + if (request.nextUrl.pathname === '/urls/relative-request') { + return fetch(new Request('/urls/urls-b')) + } + + if (request.nextUrl.pathname === '/urls/relative-redirect') { + return Response.redirect('/urls/urls-b') + } + + if (request.nextUrl.pathname === '/urls/relative-next-redirect') { + return NextResponse.redirect('/urls/urls-b') + } + + if (request.nextUrl.pathname === '/urls/relative-next-rewrite') { + return NextResponse.rewrite('/urls/urls-b') + } + + if (request.nextUrl.pathname === '/urls/relative-next-request') { + return fetch(new NextRequest('/urls/urls-b')) + } + } catch (error) { + return NextResponse.json({ + error: { + message: error.message, + }, + }) + } +} diff --git a/test/integration/middleware/core/pages/urls/index.js b/test/integration/middleware/core/pages/urls/index.js new file mode 100644 index 00000000000000..a1a42f2f393758 --- /dev/null +++ b/test/integration/middleware/core/pages/urls/index.js @@ -0,0 +1,3 @@ +export default function URLsA() { + returnURLs A
+} diff --git a/test/integration/middleware/core/pages/urls/urls-b.js b/test/integration/middleware/core/pages/urls/urls-b.js new file mode 100644 index 00000000000000..19326a7b8d8acc --- /dev/null +++ b/test/integration/middleware/core/pages/urls/urls-b.js @@ -0,0 +1,3 @@ +export default function URLsB() { + returnURLs B
+} diff --git a/test/integration/middleware/core/test/index.test.js b/test/integration/middleware/core/test/index.test.js index 25f4e01634ab36..32e2b99c2f4a21 100644 --- a/test/integration/middleware/core/test/index.test.js +++ b/test/integration/middleware/core/test/index.test.js @@ -19,18 +19,20 @@ const context = {} context.appDir = join(__dirname, '../') const middlewareWarning = 'using beta Middleware (not covered by semver)' +const urlsWarning = 'using relative URLs for Middleware will be deprecated soon' describe('Middleware base tests', () => { describe('dev mode', () => { - let output = '' + const log = { output: '' } + beforeAll(async () => { context.appPort = await findPort() context.app = await launchApp(context.appDir, context.appPort, { onStdout(msg) { - output += msg + log.output += msg }, onStderr(msg) { - output += msg + log.output += msg }, }) }) @@ -43,14 +45,16 @@ describe('Middleware base tests', () => { responseTests('/fr') interfaceTests() interfaceTests('/fr') + urlTests(log) + urlTests(log, '/fr') it('should have showed warning for middleware usage', () => { - expect(output).toContain(middlewareWarning) + expect(log.output).toContain(middlewareWarning) }) }) describe('production mode', () => { + let serverOutput = { output: '' } let buildOutput - let serverOutput beforeAll(async () => { const res = await nextBuild(context.appDir, undefined, { @@ -62,10 +66,10 @@ describe('Middleware base tests', () => { context.appPort = await findPort() context.app = await nextStart(context.appDir, context.appPort, { onStdout(msg) { - serverOutput += msg + serverOutput.output += msg }, onStderr(msg) { - serverOutput += msg + serverOutput.output += msg }, }) }) @@ -78,13 +82,15 @@ describe('Middleware base tests', () => { responseTests('/fr') interfaceTests() interfaceTests('/fr') + urlTests(serverOutput) + urlTests(serverOutput, '/fr') it('should have middleware warning during build', () => { expect(buildOutput).toContain(middlewareWarning) }) it('should have middleware warning during start', () => { - expect(serverOutput).toContain(middlewareWarning) + expect(serverOutput.output).toContain(middlewareWarning) }) it('should have correct files in manifest', async () => { @@ -104,6 +110,57 @@ describe('Middleware base tests', () => { }) }) +function urlTests(log, locale = '') { + it('rewrites by default to a target location', async () => { + const res = await fetchViaHTTP(context.appPort, `${locale}/urls`) + const html = await res.text() + const $ = cheerio.load(html) + expect($('.title').text()).toBe('URLs A') + }) + + it('throws when using URL with a relative URL', async () => { + const res = await fetchViaHTTP( + context.appPort, + `${locale}/urls/relative-url` + ) + const json = await res.json() + expect(json.error.message).toContain('Invalid URL') + }) + + it('throws when using Request with a relative URL', async () => { + const res = await fetchViaHTTP( + context.appPort, + `${locale}/urls/relative-request` + ) + const json = await res.json() + expect(json.error.message).toContain('Invalid URL') + }) + + it('throws when using NextRequest with a relative URL', async () => { + const res = await fetchViaHTTP( + context.appPort, + `${locale}/urls/relative-next-request` + ) + const json = await res.json() + expect(json.error.message).toContain('Invalid URL') + }) + + it('warns when using Response.redirect with a relative URL', async () => { + await fetchViaHTTP(context.appPort, `${locale}/urls/relative-redirect`) + expect(log.output).toContain(urlsWarning) + }) + + it('warns when using NextResponse.redirect with a relative URL', async () => { + await fetchViaHTTP(context.appPort, `${locale}/urls/relative-next-redirect`) + expect(log.output).toContain(urlsWarning) + }) + + it('warns when using NextResponse.rewrite with a relative URL', async () => { + await fetchViaHTTP(context.appPort, `${locale}/urls/relative-next-rewrite`) + expect(log.output).toContain(urlsWarning) + }) +} + function rewriteTests(locale = '') { it('should rewrite to fallback: true page successfully', async () => { const randomSlug = `another-${Date.now()}` diff --git a/test/integration/middleware/hmr/pages/about/_middleware.js b/test/integration/middleware/hmr/pages/about/_middleware.js index 64dba899b58b9a..66902bd191d7f9 100644 --- a/test/integration/middleware/hmr/pages/about/_middleware.js +++ b/test/integration/middleware/hmr/pages/about/_middleware.js @@ -1,5 +1,5 @@ import { NextResponse } from 'next/server' -export function middleware() { - return NextResponse.rewrite('/about/a') +export function middleware(request) { + return NextResponse.rewrite(new URL('/about/a', request.url)) } diff --git a/test/integration/react-streaming-and-server-components/app/components/container.server.js b/test/integration/react-streaming-and-server-components/app/components/container.server.js new file mode 100644 index 00000000000000..ab63ebb0d37f0f --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/components/container.server.js @@ -0,0 +1,3 @@ +export default function Container({ children }) { + returncatch-all page
+} diff --git a/test/production/fallback-export-error/pages/index.js b/test/production/fallback-export-error/pages/index.js new file mode 100644 index 00000000000000..08263e34c35fd2 --- /dev/null +++ b/test/production/fallback-export-error/pages/index.js @@ -0,0 +1,3 @@ +export default function Page() { + returnindex page
+} diff --git a/test/production/middleware-typescript/test/index.test.ts b/test/production/middleware-typescript/test/index.test.ts index 6a1ca2c66492d4..dd04154749ac33 100644 --- a/test/production/middleware-typescript/test/index.test.ts +++ b/test/production/middleware-typescript/test/index.test.ts @@ -19,6 +19,7 @@ describe('should set-up next', () => { }, dependencies: { typescript: 'latest', + '@types/node': 'latest', '@types/react': 'latest', '@types/react-dom': 'latest', }, diff --git a/test/production/typescript-basic/app/pages/index.tsx b/test/production/typescript-basic/app/pages/index.tsx new file mode 100644 index 00000000000000..828b8e36d67646 --- /dev/null +++ b/test/production/typescript-basic/app/pages/index.tsx @@ -0,0 +1,15 @@ +import { useRouter } from 'next/router' +import Link from 'next/link' + +export default function Page() { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const router = useRouter() + return ( + <> +hello world
+ + to /another + + > + ) +} diff --git a/test/production/typescript-basic/app/pages/styled-jsx.tsx b/test/production/typescript-basic/app/pages/styled-jsx.tsx new file mode 100644 index 00000000000000..5482beb5dbd8d3 --- /dev/null +++ b/test/production/typescript-basic/app/pages/styled-jsx.tsx @@ -0,0 +1,27 @@ +import css from 'styled-jsx/css' + +const divStyles = css` + div { + color: orange; + } +` + +export default function Page(props) { + return ( + <> +styled p tag (should be pink)
+ + + + > + ) +} diff --git a/test/production/typescript-basic.test.ts b/test/production/typescript-basic/index.test.ts similarity index 62% rename from test/production/typescript-basic.test.ts rename to test/production/typescript-basic/index.test.ts index 096742f9186f85..404a67ad0f4ed5 100644 --- a/test/production/typescript-basic.test.ts +++ b/test/production/typescript-basic/index.test.ts @@ -1,4 +1,5 @@ -import { createNext } from 'e2e-utils' +import path from 'path' +import { createNext, FileRef } from 'e2e-utils' import { renderViaHTTP } from 'next-test-utils' import { NextInstance } from 'test/lib/next-modes/base' @@ -8,22 +9,7 @@ describe('TypeScript basic', () => { beforeAll(async () => { next = await createNext({ files: { - 'pages/index.tsx': ` - import { useRouter } from 'next/router' - import Link from 'next/link' - - export default function Page() { - const router = useRouter() - return ( - <> -hello world
- - to /another - - > - ) - } - `, + pages: new FileRef(path.join(__dirname, 'app/pages')), 'server.ts': ` import next from 'next'; const app = next({ @@ -39,7 +25,9 @@ describe('TypeScript basic', () => { }, dependencies: { typescript: '4.4.3', - '@types/react': '16.9.17', + '@types/node': 'latest', + '@types/react': 'latest', + '@types/react-dom': 'latest', }, }) }) diff --git a/test/readme.md b/test/readme.md index 186cd8bcf5ad45..7e278e911369c0 100644 --- a/test/readme.md +++ b/test/readme.md @@ -12,7 +12,7 @@ You can set-up a new test using `yarn new-test` which will start from a template - integration: These tests run misc checks and modes and is where tests used to be added before we added more specific folders. Ideally we don't add new test suites here as tests here are not isolated from the monorepo. - unit: These are very fast tests that should run without a browser or running next and should be testing a specific utility. -For the e2e, production, and development tests the `createNext` utility should be used and an example is available [here](./e2e/example.test.txt). This creates an isolated Next.js install to ensure nothing in the monorepo is relied on accidentally causing incorrect tests. +For the e2e, production, and development tests the `createNext` utility should be used and an example is available [here](./e2e/example.txt). This creates an isolated Next.js install to ensure nothing in the monorepo is relied on accidentally causing incorrect tests. All new test suites should be written in TypeScript either `.ts` (or `.tsx` for unit tests). This will help ensure we catch smaller issues in tests that could cause flakey or incorrect tests. diff --git a/test/unit/web-runtime/next-url.test.ts b/test/unit/web-runtime/next-url.test.ts index e3914a938e9201..ed13e63eb217f5 100644 --- a/test/unit/web-runtime/next-url.test.ts +++ b/test/unit/web-runtime/next-url.test.ts @@ -170,3 +170,21 @@ it('allows to change the port', () => { url.port = '' expect(url.href).toEqual('https://localhost/foo') }) + +it('allows to clone a new copy', () => { + const url = new NextURL('/root/es/bar', { + base: 'http://127.0.0.1', + basePath: '/root', + i18n: { + defaultLocale: 'en', + locales: ['en', 'es', 'fr'], + }, + }) + + const clone = url.clone() + clone.pathname = '/test' + clone.basePath = '/root-test' + + expect(url.toString()).toEqual('http://localhost/root/es/bar') + expect(clone.toString()).toEqual('http://localhost/root-test/es/test') +}) diff --git a/test/unit/webpack-config-overrides.test.ts b/test/unit/webpack-config-overrides.test.ts index c6c0a877b37a9b..41bbe59044be8b 100644 --- a/test/unit/webpack-config-overrides.test.ts +++ b/test/unit/webpack-config-overrides.test.ts @@ -26,12 +26,20 @@ describe('webpack-config attachReactRefresh', () => { it('should skip adding when existing (shorthand)', () => { const input = { module: { - rules: [{ use: ['@next/react-refresh-utils/loader', 'rr'] }], + rules: [ + { + use: ['next/dist/compiled/@next/react-refresh-utils/loader', 'rr'], + }, + ], }, } const expected = { module: { - rules: [{ use: ['@next/react-refresh-utils/loader', 'rr'] }], + rules: [ + { + use: ['next/dist/compiled/@next/react-refresh-utils/loader', 'rr'], + }, + ], }, } @@ -43,14 +51,28 @@ describe('webpack-config attachReactRefresh', () => { const input = { module: { rules: [ - { use: [require.resolve('@next/react-refresh-utils/loader'), 'rr'] }, + { + use: [ + require.resolve( + 'next/dist/compiled/@next/react-refresh-utils/loader' + ), + 'rr', + ], + }, ], }, } const expected = { module: { rules: [ - { use: [require.resolve('@next/react-refresh-utils/loader'), 'rr'] }, + { + use: [ + require.resolve( + 'next/dist/compiled/@next/react-refresh-utils/loader' + ), + 'rr', + ], + }, ], }, } @@ -184,7 +206,7 @@ describe('webpack-config attachReactRefresh', () => { use: [ 'hehe', 'haha', - '@next/react-refresh-utils/loader', + 'next/dist/compiled/@next/react-refresh-utils/loader', 'rr', 'lol', 'bla', @@ -202,7 +224,7 @@ describe('webpack-config attachReactRefresh', () => { use: [ 'hehe', 'haha', - '@next/react-refresh-utils/loader', + 'next/dist/compiled/@next/react-refresh-utils/loader', 'rr', 'lol', 'bla', @@ -223,7 +245,7 @@ describe('webpack-config attachReactRefresh', () => { 'haha', 'rr', 'lol', - '@next/react-refresh-utils/loader', + 'next/dist/compiled/@next/react-refresh-utils/loader', 'bla', ], }, @@ -241,7 +263,7 @@ describe('webpack-config attachReactRefresh', () => { 'haha', 'rr', 'lol', - '@next/react-refresh-utils/loader', + 'next/dist/compiled/@next/react-refresh-utils/loader', 'bla', ], }, diff --git a/yarn.lock b/yarn.lock index 1ba533e5b0e4eb..893a225963103a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7643,7 +7643,7 @@ conventional-recommended-bump@^6.1.0: meow "^8.0.0" q "^1.5.1" -convert-source-map@1.7.0, convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: +convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" dependencies: @@ -8762,10 +8762,6 @@ emoji-regex@^9.2.2: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== -emojis-list@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" - emojis-list@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" @@ -13140,14 +13136,6 @@ loader-runner@^4.2.0: dependencies: big.js "^6.1.1" -loader-utils@1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" - dependencies: - big.js "^5.2.2" - emojis-list "^2.0.0" - json5 "^1.0.1" - loader-utils@^1.1.0, loader-utils@^1.2.3: version "1.4.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" @@ -14423,6 +14411,13 @@ node-fetch@2.6.1, node-fetch@^2.1.1, node-fetch@^2.2.0, node-fetch@^2.6.0, node- resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== +node-fetch@2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + node-fetch@^1.0.1: version "1.7.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" @@ -18379,10 +18374,6 @@ source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" -source-map@0.7.3, source-map@^0.7.3, source-map@~0.7.2: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - source-map@0.8.0-beta.0: version "0.8.0-beta.0" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.8.0-beta.0.tgz#d4c1bb42c3f7ee925f005927ba10709e0d1d1f11" @@ -18399,6 +18390,10 @@ source-map@^0.5.0, source-map@^0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" +source-map@^0.7.3, source-map@~0.7.2: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + sourcemap-codec@^1.4.4: version "1.4.8" resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" @@ -18911,19 +18906,10 @@ styled-jsx-plugin-postcss@3.0.2: postcss "^7.0.2" postcss-load-plugins "^2.3.0" -styled-jsx@5.0.0-beta.6: - version "5.0.0-beta.6" - resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.0.0-beta.6.tgz#666552f8831a06f80c9084a47afc4b32b0c9f461" - integrity sha512-b1cM7Xyp2r1lsNpvoZ6wmTI8qxD0557vH2feHakNU8LMkzfJDgTQMul6O7sSYY0GxQ73pKEN69hCDp71w6Q0nA== - dependencies: - "@babel/plugin-syntax-jsx" "7.14.5" - "@babel/types" "7.15.0" - convert-source-map "1.7.0" - loader-utils "1.2.3" - source-map "0.7.3" - string-hash "1.1.3" - stylis "3.5.4" - stylis-rule-sheet "0.0.10" +styled-jsx@5.0.0-beta.7: + version "5.0.0-beta.7" + resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.0.0-beta.7.tgz#d952f888576b1390897b2b2a026eb43eeef7ee85" + integrity sha512-fCrifrkBKt+SM6aNDNnRWiV0YP7JJ6HRLCofyfbseo0RFhbxD2pka5zG5yH5Ym8KOChUrEFbibkjWeSB7n6IwQ== stylehacks@^4.0.0: version "4.0.3" @@ -18933,14 +18919,6 @@ stylehacks@^4.0.0: postcss "^7.0.0" postcss-selector-parser "^3.0.0" -stylis-rule-sheet@0.0.10: - version "0.0.10" - resolved "https://registry.yarnpkg.com/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz#44e64a2b076643f4b52e5ff71efc04d8c3c4a430" - -stylis@3.5.4: - version "3.5.4" - resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.4.tgz#f665f25f5e299cf3d64654ab949a57c768b73fbe" - superagent@^3.8.1: version "3.8.3" resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.3.tgz#460ea0dbdb7d5b11bc4f78deba565f86a178e128" @@ -19473,6 +19451,11 @@ tr46@^2.1.0: dependencies: punycode "^2.1.1" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + traverse@0.6.6: version "0.6.6" resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137" @@ -20507,6 +20490,11 @@ webcrypto-core@^1.2.0: pvtsutils "^1.2.0" tslib "^2.3.1" +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" @@ -20646,6 +20634,14 @@ whatwg-mimetype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + whatwg-url@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06"