diff --git a/packages/next/client/link.tsx b/packages/next/client/link.tsx index 88d8185d7ea1..4c7929c20663 100644 --- a/packages/next/client/link.tsx +++ b/packages/next/client/link.tsx @@ -30,18 +30,21 @@ type InternalLinkProps = { prefetch?: boolean locale?: string | false legacyBehavior?: boolean + // e: any because as it would otherwise overlap with existing types /** * requires experimental.newNextLinkBehavior */ - onMouseEnter?: (e: React.MouseEvent) => void + onMouseEnter?: (e: any) => void + // e: any because as it would otherwise overlap with existing types /** * requires experimental.newNextLinkBehavior */ - onClick?: (e: React.MouseEvent) => void + onClick?: (e: any) => void } -export type LinkProps = InternalLinkProps & - React.AnchorHTMLAttributes +// TODO: Include the full set of Anchor props +// adding this to the publicly exported type currently breaks existing apps +export type LinkProps = InternalLinkProps type LinkPropsRequired = RequiredKeys type LinkPropsOptional = OptionalKeys @@ -116,7 +119,12 @@ function linkClicked( }) } -function Link(props: React.PropsWithChildren) { +function Link( + props: React.PropsWithChildren< + Omit, keyof LinkProps> & + LinkProps + > +) { const { legacyBehavior = Boolean(process.env.__NEXT_NEW_LINK_BEHAVIOR) !== true, } = props diff --git a/test/e2e/new-link-behavior/index.test.ts b/test/e2e/new-link-behavior/index.test.ts index b406c3a1bb3d..4330dc2c6768 100644 --- a/test/e2e/new-link-behavior/index.test.ts +++ b/test/e2e/new-link-behavior/index.test.ts @@ -18,7 +18,7 @@ async function matchLogs(browser, includes: string) { return found } -const appDir = path.join(__dirname, './app') +const appDir = path.join(__dirname, 'app') describe('New Link Behavior', () => { let next: NextInstance diff --git a/test/e2e/new-link-behavior/material-ui.test.ts b/test/e2e/new-link-behavior/material-ui.test.ts new file mode 100644 index 000000000000..0f72ebd47b6c --- /dev/null +++ b/test/e2e/new-link-behavior/material-ui.test.ts @@ -0,0 +1,46 @@ +import { createNext, FileRef } from 'e2e-utils' +import { NextInstance } from 'test/lib/next-modes/base' +import webdriver from 'next-webdriver' +import path from 'path' + +const appDir = path.join(__dirname, 'material-ui') + +describe('New Link Behavior with material-ui', () => { + let next: NextInstance + + beforeAll(async () => { + next = await createNext({ + files: { + pages: new FileRef(path.join(appDir, 'pages')), + src: new FileRef(path.join(appDir, 'src')), + 'next.config.js': new FileRef(path.join(appDir, 'next.config.js')), + }, + dependencies: { + '@emotion/cache': 'latest', + '@emotion/react': 'latest', + '@emotion/server': 'latest', + '@emotion/styled': 'latest', + '@mui/icons-material': 'latest', + '@mui/material': 'latest', + next: 'latest', + 'prop-types': 'latest', + react: 'latest', + 'react-dom': 'latest', + eslint: 'latest', + 'eslint-config-next': 'latest', + }, + }) + }) + afterAll(() => next.destroy()) + + it('should render MuiLink with ', async () => { + const browser = await webdriver(next.url, `/`) + const element = await browser.elementByCss('a[href="/about"]') + + const color = await element.getComputedCss('color') + expect(color).toBe('rgb(25, 133, 123)') + + const text = await element.text() + expect(text).toBe('Go to the about page') + }) +}) diff --git a/test/e2e/new-link-behavior/material-ui/next.config.js b/test/e2e/new-link-behavior/material-ui/next.config.js new file mode 100644 index 000000000000..a472501c6ac7 --- /dev/null +++ b/test/e2e/new-link-behavior/material-ui/next.config.js @@ -0,0 +1,6 @@ +module.exports = { + reactStrictMode: true, + experimental: { + newNextLinkBehavior: true, + }, +} diff --git a/test/e2e/new-link-behavior/material-ui/pages/_app.js b/test/e2e/new-link-behavior/material-ui/pages/_app.js new file mode 100644 index 000000000000..e8ed2b4f2c15 --- /dev/null +++ b/test/e2e/new-link-behavior/material-ui/pages/_app.js @@ -0,0 +1,27 @@ +import * as React from 'react' +import Head from 'next/head' +import { ThemeProvider } from '@mui/material/styles' +import CssBaseline from '@mui/material/CssBaseline' +import { CacheProvider } from '@emotion/react' +import theme from '../src/theme' +import createEmotionCache from '../src/createEmotionCache' + +// Client-side cache, shared for the whole session of the user in the browser. +const clientSideEmotionCache = createEmotionCache() + +export default function MyApp(props) { + const { Component, emotionCache = clientSideEmotionCache, pageProps } = props + + return ( + + + + + + {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */} + + + + + ) +} diff --git a/test/e2e/new-link-behavior/material-ui/pages/_document.js b/test/e2e/new-link-behavior/material-ui/pages/_document.js new file mode 100644 index 000000000000..cd639686df4e --- /dev/null +++ b/test/e2e/new-link-behavior/material-ui/pages/_document.js @@ -0,0 +1,88 @@ +import * as React from 'react' +import Document, { Html, Head, Main, NextScript } from 'next/document' +import createEmotionServer from '@emotion/server/create-instance' +import theme from '../src/theme' +import createEmotionCache from '../src/createEmotionCache' + +export default class MyDocument extends Document { + render() { + return ( + + + {/* PWA primary color */} + + + + {/* Inject MUI styles first to match with the prepend: true configuration. */} + {this.props.emotionStyleTags} + + +
+ + + + ) + } +} + +// `getInitialProps` belongs to `_document` (instead of `_app`), +// it's compatible with static-site generation (SSG). +MyDocument.getInitialProps = async (ctx) => { + // Resolution order + // + // On the server: + // 1. app.getInitialProps + // 2. page.getInitialProps + // 3. document.getInitialProps + // 4. app.render + // 5. page.render + // 6. document.render + // + // On the server with error: + // 1. document.getInitialProps + // 2. app.render + // 3. page.render + // 4. document.render + // + // On the client + // 1. app.getInitialProps + // 2. page.getInitialProps + // 3. app.render + // 4. page.render + + const originalRenderPage = ctx.renderPage + + // You can consider sharing the same emotion cache between all the SSR requests to speed up performance. + // However, be aware that it can have global side effects. + const cache = createEmotionCache() + const { extractCriticalToChunks } = createEmotionServer(cache) + + ctx.renderPage = () => + originalRenderPage({ + enhanceApp: (App) => + function EnhanceApp(props) { + return + }, + }) + + const initialProps = await Document.getInitialProps(ctx) + // This is important. It prevents emotion to render invalid HTML. + // See https://github.com/mui/material-ui/issues/26561#issuecomment-855286153 + const emotionStyles = extractCriticalToChunks(initialProps.html) + const emotionStyleTags = emotionStyles.styles.map((style) => ( +