Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Allow mismatching href and as when manually provided #9837

Merged
merged 11 commits into from Jan 20, 2020
38 changes: 25 additions & 13 deletions packages/next/next-server/lib/router/router.ts
Expand Up @@ -331,22 +331,34 @@ export default class Router implements BaseRouter {

if (isDynamicRoute(route)) {
const { pathname: asPathname } = parse(as)
const routeMatch = getRouteMatcher(getRouteRegex(route))(asPathname)
const routeRegex = getRouteRegex(route)
const routeMatch = getRouteMatcher(routeRegex)(asPathname)
if (!routeMatch) {
const error =
`The provided \`as\` value (${asPathname}) is incompatible with the \`href\` value (${route}). ` +
`Read more: https://err.sh/zeit/next.js/incompatible-href-as`

if (process.env.NODE_ENV !== 'production') {
throw new Error(error)
} else {
console.error(error)
const missingParams = Object.keys(routeRegex.groups).filter(
param => !query[param]
)

if (missingParams.length > 0) {
if (process.env.NODE_ENV !== 'production') {
console.warn(
`Mismatching \`as\` and \`href\` failed to manually provide ` +
`the params: ${missingParams.join(
', '
)} in the \`href\`'s \`query\``
)
}

return reject(
new Error(
`The provided \`as\` value (${asPathname}) is incompatible with the \`href\` value (${route}). ` +
`Read more: https://err.sh/zeit/next.js/incompatible-href-as`
)
)
}
return resolve(false)
} else {
// Merge params into `query`, overwriting any specified in search
Object.assign(query, routeMatch)
}

// Merge params into `query`, overwriting any specified in search
Object.assign(query, routeMatch)
}

Router.events.emit('routeChangeStart', as)
Expand Down
1 change: 1 addition & 0 deletions test/integration/invalid-href/pages/[post].js
@@ -0,0 +1 @@
export default () => 'hi from post'
@@ -0,0 +1,7 @@
import Link from 'next/link'

export default () => (
<Link href="/[post]?post=post-1" as="/blog/post-1">
<a>Click me</a>
</Link>
)
68 changes: 65 additions & 3 deletions test/integration/invalid-href/test/index.test.js
Expand Up @@ -20,13 +20,45 @@ const appDir = join(__dirname, '..')
const firstErrorRegex = /Invalid href passed to router: mailto:idk@idk.com.*invalid-href-passed/
const secondErrorRegex = /Invalid href passed to router: .*google\.com.*invalid-href-passed/

const showsError = async (pathname, regex, click = false) => {
const showsError = async (
pathname,
regex,
click = false,
isWarn = false,
cb
) => {
const browser = await webdriver(appPort, pathname)
if (isWarn) {
await browser.eval(`(function() {
window.warnLogs = []
var origWarn = window.console.warn
window.console.warn = function() {
var warnStr = ''
for (var i = 0; i < arguments.length; i++) {
if (i > 0) warnStr += ' ';
warnStr += arguments[i]
}
window.warnLogs.push(warnStr)
origWarn.apply(undefined, arguments)
}
})()`)
}

if (click) {
await browser.elementByCss('a').click()
}
const errorContent = await getReactErrorOverlayContent(browser)
expect(errorContent).toMatch(regex)
if (isWarn) {
await waitFor(2000)
const warnLogs = await browser.eval('window.warnLogs')
console.log(warnLogs)
expect(warnLogs.some(log => log.match(regex))).toBe(true)
} else {
const errorContent = await getReactErrorOverlayContent(browser)
expect(errorContent).toMatch(regex)
}

if (cb) await cb(browser)

await browser.close()
}

Expand Down Expand Up @@ -89,6 +121,16 @@ describe('Invalid hrefs', () => {
/The provided `as` value \(\/blog\/post-1\) is incompatible with the `href` value \(\/\[post\]\)/,
true
)
await showsError(
'/dynamic-route-mismatch',
/Mismatching `as` and `href` failed to manually provide the params: post in the `href`'s `query`/,
true,
true
)
})

it('does not throw error when dynamic route mismatch is used on Link and params are manually provided', async () => {
await noError('/dynamic-route-mismatch-manual', true)
})
})

Expand Down Expand Up @@ -123,5 +165,25 @@ describe('Invalid hrefs', () => {
it('does not show error in production when https://google.com is used as href on router.replace', async () => {
await noError('/second?method=replace', true)
})

it('shows error when dynamic route mismatch is used on Link', async () => {
const browser = await webdriver(appPort, '/dynamic-route-mismatch')
await browser.eval(`(function() {
window.caughtErrors = []
window.addEventListener('unhandledrejection', (error) => {
window.caughtErrors.push(error.reason.message)
})
})()`)
await browser.elementByCss('a').click()
await waitFor(500)
const errors = await browser.eval('window.caughtErrors')
expect(
errors.find(err =>
err.includes(
'The provided `as` value (/blog/post-1) is incompatible with the `href` value (/[post]). Read more: https://err.sh/zeit/next.js/incompatible-href-as'
)
)
).toBeTruthy()
})
})
})