From ac5b2acd12f061c2a7cfeee414bad4f80212c7c6 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Mon, 29 Jun 2020 16:44:43 -0400 Subject: [PATCH] Fix dynamic route encoding for NextLinks (#14281) --- packages/next/package.json | 2 +- .../link-with-encoding/pages/index.js | 36 +++ .../link-with-encoding/pages/single/[slug].js | 7 + .../link-with-encoding/test/index.test.js | 299 ++++++++++++++++++ yarn.lock | 8 +- 5 files changed, 347 insertions(+), 5 deletions(-) create mode 100644 test/integration/link-with-encoding/pages/index.js create mode 100644 test/integration/link-with-encoding/pages/single/[slug].js create mode 100644 test/integration/link-with-encoding/test/index.test.js diff --git a/packages/next/package.json b/packages/next/package.json index ba073a8b92..c03f9269dd 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -90,7 +90,7 @@ "loader-utils": "2.0.0", "mini-css-extract-plugin": "0.8.0", "mkdirp": "0.5.3", - "native-url": "0.3.3", + "native-url": "0.3.4", "neo-async": "2.6.1", "pnp-webpack-plugin": "1.6.4", "postcss": "7.0.29", diff --git a/test/integration/link-with-encoding/pages/index.js b/test/integration/link-with-encoding/pages/index.js new file mode 100644 index 0000000000..6534196c28 --- /dev/null +++ b/test/integration/link-with-encoding/pages/index.js @@ -0,0 +1,36 @@ +import Link from 'next/link' + +const Home = () => ( +
+ + Single: Spaces + +
+ + Single: Percent + +
+ + Single: Forward Slash + +
+ + Single: " + +
+ + Single: : + +
+) + +export default Home diff --git a/test/integration/link-with-encoding/pages/single/[slug].js b/test/integration/link-with-encoding/pages/single/[slug].js new file mode 100644 index 0000000000..82eaad1074 --- /dev/null +++ b/test/integration/link-with-encoding/pages/single/[slug].js @@ -0,0 +1,7 @@ +export function getServerSideProps({ params: { slug } }) { + return { props: { slug } } +} + +export default function Single(props) { + return
{JSON.stringify(props)}
+} diff --git a/test/integration/link-with-encoding/test/index.test.js b/test/integration/link-with-encoding/test/index.test.js new file mode 100644 index 0000000000..63bd57d2c4 --- /dev/null +++ b/test/integration/link-with-encoding/test/index.test.js @@ -0,0 +1,299 @@ +/* eslint-env jest */ + +import { findPort, killApp, launchApp, waitFor, check } from 'next-test-utils' +import webdriver from 'next-webdriver' +import { join } from 'path' + +jest.setTimeout(1000 * 60 * 2) + +const appDir = join(__dirname, '..') + +let appPort +let app + +describe('Link Component with Encoding', () => { + beforeAll(async () => { + appPort = await findPort() + app = await launchApp(appDir, appPort) + }) + afterAll(() => killApp(app)) + + describe('spaces', () => { + it('should have correct query on SSR', async () => { + const browser = await webdriver( + appPort, + encodeURI('/single/hello world ') + ) + try { + const text = await browser.elementByCss('#query-content').text() + expect(text).toMatchInlineSnapshot(`"{\\"slug\\":\\"hello world \\"}"`) + } finally { + await browser.close() + } + }) + + it('should have correct query on Router#push', async () => { + const browser = await webdriver(appPort, '/') + try { + await waitFor(2000) + await browser.eval( + `window.next.router.push( + { pathname: '/single/[slug]' }, + { pathname: encodeURI('/single/hello world ') } + )` + ) + await check(() => browser.hasElementByCssSelector('#query-content'), { + test(val) { + return Boolean(val) + }, + }) + const text = await browser.elementByCss('#query-content').text() + expect(text).toMatchInlineSnapshot(`"{\\"slug\\":\\"hello world \\"}"`) + } finally { + await browser.close() + } + }) + + it('should have correct query on simple client-side ', async () => { + const browser = await webdriver(appPort, '/') + try { + await waitFor(2000) + await browser.elementByCss('#single-spaces').click() + await check(() => browser.hasElementByCssSelector('#query-content'), { + test(val) { + return Boolean(val) + }, + }) + const text = await browser.elementByCss('#query-content').text() + expect(text).toMatchInlineSnapshot(`"{\\"slug\\":\\"hello world \\"}"`) + } finally { + await browser.close() + } + }) + }) + + describe('percent', () => { + it('should have correct query on SSR', async () => { + const browser = await webdriver(appPort, encodeURI('/single/hello%world')) + try { + const text = await browser.elementByCss('#query-content').text() + expect(text).toMatchInlineSnapshot(`"{\\"slug\\":\\"hello%world\\"}"`) + } finally { + await browser.close() + } + }) + + it('should have correct query on Router#push', async () => { + const browser = await webdriver(appPort, '/') + try { + await waitFor(2000) + await browser.eval( + `window.next.router.push( + { pathname: '/single/[slug]' }, + { pathname: encodeURI('/single/hello%world') } + )` + ) + await check(() => browser.hasElementByCssSelector('#query-content'), { + test(val) { + return Boolean(val) + }, + }) + const text = await browser.elementByCss('#query-content').text() + expect(text).toMatchInlineSnapshot(`"{\\"slug\\":\\"hello%world\\"}"`) + } finally { + await browser.close() + } + }) + + it('should have correct query on simple client-side ', async () => { + const browser = await webdriver(appPort, '/') + try { + await waitFor(2000) + await browser.elementByCss('#single-percent').click() + await check(() => browser.hasElementByCssSelector('#query-content'), { + test(val) { + return Boolean(val) + }, + }) + const text = await browser.elementByCss('#query-content').text() + expect(text).toMatchInlineSnapshot(`"{\\"slug\\":\\"hello%world\\"}"`) + } finally { + await browser.close() + } + }) + }) + + describe('forward slash', () => { + it('should have correct query on SSR', async () => { + const browser = await webdriver( + appPort, + `/single/hello${encodeURIComponent('/')}world` + ) + try { + const text = await browser.elementByCss('#query-content').text() + expect(text).toMatchInlineSnapshot(`"{\\"slug\\":\\"hello/world\\"}"`) + } finally { + await browser.close() + } + }) + + it('should have correct query on Router#push', async () => { + const browser = await webdriver(appPort, '/') + try { + await waitFor(2000) + await browser.eval( + `window.next.router.push( + { pathname: '/single/[slug]' }, + { pathname: '/single/hello${encodeURIComponent('/')}world' } + )` + ) + await check(() => browser.hasElementByCssSelector('#query-content'), { + test(val) { + return Boolean(val) + }, + }) + const text = await browser.elementByCss('#query-content').text() + expect(text).toMatchInlineSnapshot(`"{\\"slug\\":\\"hello/world\\"}"`) + } finally { + await browser.close() + } + }) + + it('should have correct query on simple client-side ', async () => { + const browser = await webdriver(appPort, '/') + try { + await waitFor(2000) + await browser.elementByCss('#single-slash').click() + await check(() => browser.hasElementByCssSelector('#query-content'), { + test(val) { + return Boolean(val) + }, + }) + const text = await browser.elementByCss('#query-content').text() + expect(text).toMatchInlineSnapshot(`"{\\"slug\\":\\"hello/world\\"}"`) + } finally { + await browser.close() + } + }) + }) + + describe('double quote', () => { + it('should have correct query on SSR', async () => { + const browser = await webdriver( + appPort, + `/single/hello${encodeURIComponent('"')}world` + ) + try { + const text = await browser.elementByCss('#query-content').text() + expect(JSON.parse(text)).toMatchInlineSnapshot(` + Object { + "slug": "hello\\"world", + } + `) + } finally { + await browser.close() + } + }) + + it('should have correct query on Router#push', async () => { + const browser = await webdriver(appPort, '/') + try { + await waitFor(2000) + await browser.eval( + `window.next.router.push( + { pathname: '/single/[slug]' }, + { pathname: '/single/hello${encodeURIComponent('"')}world' } + )` + ) + await check(() => browser.hasElementByCssSelector('#query-content'), { + test(val) { + return Boolean(val) + }, + }) + const text = await browser.elementByCss('#query-content').text() + expect(JSON.parse(text)).toMatchInlineSnapshot(` + Object { + "slug": "hello\\"world", + } + `) + } finally { + await browser.close() + } + }) + + it('should have correct query on simple client-side ', async () => { + const browser = await webdriver(appPort, '/') + try { + await waitFor(2000) + await browser.elementByCss('#single-double-quote').click() + await check(() => browser.hasElementByCssSelector('#query-content'), { + test(val) { + return Boolean(val) + }, + }) + const text = await browser.elementByCss('#query-content').text() + expect(JSON.parse(text)).toMatchInlineSnapshot(` + Object { + "slug": "hello\\"world", + } + `) + } finally { + await browser.close() + } + }) + }) + + describe('colon', () => { + it('should have correct query on SSR', async () => { + const browser = await webdriver( + appPort, + `/single/hello${encodeURIComponent(':')}world` + ) + try { + const text = await browser.elementByCss('#query-content').text() + expect(text).toMatchInlineSnapshot(`"{\\"slug\\":\\"hello:world\\"}"`) + } finally { + await browser.close() + } + }) + + it('should have correct query on Router#push', async () => { + const browser = await webdriver(appPort, '/') + try { + await waitFor(2000) + await browser.eval( + `window.next.router.push( + { pathname: '/single/[slug]' }, + { pathname: '/single/hello${encodeURIComponent(':')}world' } + )` + ) + await check(() => browser.hasElementByCssSelector('#query-content'), { + test(val) { + return Boolean(val) + }, + }) + const text = await browser.elementByCss('#query-content').text() + expect(text).toMatchInlineSnapshot(`"{\\"slug\\":\\"hello:world\\"}"`) + } finally { + await browser.close() + } + }) + + it('should have correct query on simple client-side ', async () => { + const browser = await webdriver(appPort, '/') + try { + await waitFor(2000) + await browser.elementByCss('#single-colon').click() + await check(() => browser.hasElementByCssSelector('#query-content'), { + test(val) { + return Boolean(val) + }, + }) + const text = await browser.elementByCss('#query-content').text() + expect(text).toMatchInlineSnapshot(`"{\\"slug\\":\\"hello:world\\"}"`) + } finally { + await browser.close() + } + }) + }) +}) diff --git a/yarn.lock b/yarn.lock index 853199ef5a..32bfa4d9dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10648,10 +10648,10 @@ native-or-bluebird@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/native-or-bluebird/-/native-or-bluebird-1.2.0.tgz#39c47bfd7825d1fb9ffad32210ae25daadf101c9" -native-url@0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/native-url/-/native-url-0.3.3.tgz#09300f35416a49f79f6f8ab9e3c05c53c2873666" - integrity sha512-EEHLiNEIgcTjctwlREZjUT1vdMlrmug+fr0sQ3hoP9+cO3cyhd5fJ0GTnINcnv9LtXL+NdovWNUMDIfW98l2eA== +native-url@0.3.4: + version "0.3.4" + resolved "https://registry.yarnpkg.com/native-url/-/native-url-0.3.4.tgz#29c943172aed86c63cee62c8c04db7f5756661f8" + integrity sha512-6iM8R99ze45ivyH8vybJ7X0yekIcPf5GgLV5K0ENCbmRcaRIDoj37BC8iLEmaaBfqqb8enuZ5p0uhY+lVAbAcA== dependencies: querystring "^0.2.0" -- GitLab