Skip to content

Commit c43080f

Browse files
fix: respect 'force' and 'conditions' properties on redirects (#38657) (#38664)
* move force redirects changes to main repo * empty commit * fix: conditions and add e2e test case * test: fix country condition assertion --------- Co-authored-by: Michal Piechowiak <[email protected]> (cherry picked from commit 48d311e) Co-authored-by: Jenae Janzen <[email protected]>
1 parent 4bf84f0 commit c43080f

File tree

13 files changed

+373
-38
lines changed

13 files changed

+373
-38
lines changed
Lines changed: 125 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { applyTrailingSlashOption } from "../../utils"
22

3-
Cypress.on("uncaught:exception", (err) => {
3+
Cypress.on("uncaught:exception", err => {
44
if (err.message.includes("Minified React error")) {
55
return false
66
}
@@ -14,44 +14,149 @@ describe("Redirects", () => {
1414
it("should redirect from non-existing page to existing", () => {
1515
cy.visit(applyTrailingSlashOption(`/redirect`, TRAILING_SLASH), {
1616
failOnStatusCode: false,
17-
}).waitForRouteChange()
18-
.assertRoute(applyTrailingSlashOption(`/routes/redirect/hit`, TRAILING_SLASH))
17+
})
18+
.waitForRouteChange()
19+
.assertRoute(
20+
applyTrailingSlashOption(`/routes/redirect/hit`, TRAILING_SLASH)
21+
)
1922

2023
cy.get(`h1`).should(`have.text`, `Hit`)
2124
})
2225
it("should respect that pages take precedence over redirects", () => {
23-
cy.visit(applyTrailingSlashOption(`/routes/redirect/existing`, TRAILING_SLASH), {
24-
failOnStatusCode: false,
25-
}).waitForRouteChange()
26-
.assertRoute(applyTrailingSlashOption(`/routes/redirect/existing`, TRAILING_SLASH))
26+
cy.visit(
27+
applyTrailingSlashOption(`/routes/redirect/existing`, TRAILING_SLASH),
28+
{
29+
failOnStatusCode: false,
30+
}
31+
)
32+
.waitForRouteChange()
33+
.assertRoute(
34+
applyTrailingSlashOption(`/routes/redirect/existing`, TRAILING_SLASH)
35+
)
2736

2837
cy.get(`h1`).should(`have.text`, `Existing`)
2938
})
39+
it("should respect force redirect", () => {
40+
cy.visit(
41+
applyTrailingSlashOption(
42+
`/routes/redirect/existing-force`,
43+
TRAILING_SLASH
44+
),
45+
{
46+
failOnStatusCode: false,
47+
}
48+
)
49+
.waitForRouteChange()
50+
.assertRoute(
51+
applyTrailingSlashOption(`/routes/redirect/hit`, TRAILING_SLASH)
52+
)
53+
54+
cy.get(`h1`).should(`have.text`, `Hit`)
55+
})
56+
it("should respect country condition on redirect", () => {
57+
cy.visit(
58+
applyTrailingSlashOption(
59+
`/routes/redirect/country-condition`,
60+
TRAILING_SLASH
61+
),
62+
{
63+
failOnStatusCode: false,
64+
headers: {
65+
"x-nf-country": "us",
66+
},
67+
}
68+
)
69+
.waitForRouteChange()
70+
.assertRoute(
71+
applyTrailingSlashOption(`/routes/redirect/hit-us`, TRAILING_SLASH)
72+
)
73+
74+
cy.get(`h1`).should(`have.text`, `Hit US`)
75+
76+
cy.visit(
77+
applyTrailingSlashOption(
78+
`/routes/redirect/country-condition`,
79+
TRAILING_SLASH
80+
),
81+
{
82+
failOnStatusCode: false,
83+
headers: {
84+
"x-nf-country": "de",
85+
},
86+
}
87+
)
88+
.waitForRouteChange()
89+
.assertRoute(
90+
applyTrailingSlashOption(`/routes/redirect/hit-de`, TRAILING_SLASH)
91+
)
92+
93+
cy.get(`h1`).should(`have.text`, `Hit DE`)
94+
95+
// testing fallback
96+
cy.visit(
97+
applyTrailingSlashOption(
98+
`/routes/redirect/country-condition`,
99+
TRAILING_SLASH
100+
),
101+
{
102+
failOnStatusCode: false,
103+
headers: {
104+
"x-nf-country": "fr",
105+
},
106+
}
107+
)
108+
.waitForRouteChange()
109+
.assertRoute(
110+
applyTrailingSlashOption(`/routes/redirect/hit`, TRAILING_SLASH)
111+
)
112+
113+
cy.get(`h1`).should(`have.text`, `Hit`)
114+
})
30115
it("should support hash parameter on direct visit", () => {
31-
cy.visit(applyTrailingSlashOption(`/redirect`, TRAILING_SLASH) + `#anchor`, {
32-
failOnStatusCode: false,
33-
}).waitForRouteChange()
116+
cy.visit(
117+
applyTrailingSlashOption(`/redirect`, TRAILING_SLASH) + `#anchor`,
118+
{
119+
failOnStatusCode: false,
120+
}
121+
).waitForRouteChange()
34122

35-
cy.location(`pathname`).should(`equal`, applyTrailingSlashOption(`/routes/redirect/hit`, TRAILING_SLASH))
123+
cy.location(`pathname`).should(
124+
`equal`,
125+
applyTrailingSlashOption(`/routes/redirect/hit`, TRAILING_SLASH)
126+
)
36127
cy.location(`hash`).should(`equal`, `#anchor`)
37128
cy.location(`search`).should(`equal`, ``)
38129
})
39130
it("should support search parameter on direct visit", () => {
40-
cy.visit(applyTrailingSlashOption(`/redirect`, TRAILING_SLASH) + `?query_param=hello`, {
41-
failOnStatusCode: false,
42-
}).waitForRouteChange()
131+
cy.visit(
132+
applyTrailingSlashOption(`/redirect`, TRAILING_SLASH) +
133+
`?query_param=hello`,
134+
{
135+
failOnStatusCode: false,
136+
}
137+
).waitForRouteChange()
43138

44-
cy.location(`pathname`).should(`equal`, applyTrailingSlashOption(`/routes/redirect/hit`, TRAILING_SLASH))
139+
cy.location(`pathname`).should(
140+
`equal`,
141+
applyTrailingSlashOption(`/routes/redirect/hit`, TRAILING_SLASH)
142+
)
45143
cy.location(`hash`).should(`equal`, ``)
46144
cy.location(`search`).should(`equal`, `?query_param=hello`)
47145
})
48146
it("should support search & hash parameter on direct visit", () => {
49-
cy.visit(applyTrailingSlashOption(`/redirect`, TRAILING_SLASH) + `?query_param=hello#anchor`, {
50-
failOnStatusCode: false,
51-
}).waitForRouteChange()
147+
cy.visit(
148+
applyTrailingSlashOption(`/redirect`, TRAILING_SLASH) +
149+
`?query_param=hello#anchor`,
150+
{
151+
failOnStatusCode: false,
152+
}
153+
).waitForRouteChange()
52154

53-
cy.location(`pathname`).should(`equal`, applyTrailingSlashOption(`/routes/redirect/hit`, TRAILING_SLASH))
155+
cy.location(`pathname`).should(
156+
`equal`,
157+
applyTrailingSlashOption(`/routes/redirect/hit`, TRAILING_SLASH)
158+
)
54159
cy.location(`hash`).should(`equal`, `#anchor`)
55160
cy.location(`search`).should(`equal`, `?query_param=hello`)
56161
})
57-
})
162+
})

e2e-tests/adapters/gatsby-node.ts

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,57 @@ import * as path from "path"
22
import type { GatsbyNode, GatsbyConfig } from "gatsby"
33
import { applyTrailingSlashOption } from "./utils"
44

5-
const TRAILING_SLASH = (process.env.TRAILING_SLASH || `never`) as GatsbyConfig["trailingSlash"]
5+
const TRAILING_SLASH = (process.env.TRAILING_SLASH ||
6+
`never`) as GatsbyConfig["trailingSlash"]
67

7-
export const createPages: GatsbyNode["createPages"] = ({ actions: { createRedirect, createSlice } }) => {
8+
export const createPages: GatsbyNode["createPages"] = ({
9+
actions: { createRedirect, createSlice },
10+
}) => {
811
createRedirect({
912
fromPath: applyTrailingSlashOption("/redirect", TRAILING_SLASH),
1013
toPath: applyTrailingSlashOption("/routes/redirect/hit", TRAILING_SLASH),
1114
})
1215
createRedirect({
13-
fromPath: applyTrailingSlashOption("/routes/redirect/existing", TRAILING_SLASH),
16+
fromPath: applyTrailingSlashOption(
17+
"/routes/redirect/existing",
18+
TRAILING_SLASH
19+
),
20+
toPath: applyTrailingSlashOption("/routes/redirect/hit", TRAILING_SLASH),
21+
})
22+
createRedirect({
23+
fromPath: applyTrailingSlashOption(
24+
"/routes/redirect/existing-force",
25+
TRAILING_SLASH
26+
),
27+
toPath: applyTrailingSlashOption("/routes/redirect/hit", TRAILING_SLASH),
28+
force: true,
29+
})
30+
createRedirect({
31+
fromPath: applyTrailingSlashOption(
32+
"/routes/redirect/country-condition",
33+
TRAILING_SLASH
34+
),
35+
toPath: applyTrailingSlashOption("/routes/redirect/hit-us", TRAILING_SLASH),
36+
conditions: {
37+
country: ["us"],
38+
},
39+
})
40+
createRedirect({
41+
fromPath: applyTrailingSlashOption(
42+
"/routes/redirect/country-condition",
43+
TRAILING_SLASH
44+
),
45+
toPath: applyTrailingSlashOption("/routes/redirect/hit-de", TRAILING_SLASH),
46+
conditions: {
47+
country: ["de"],
48+
},
49+
})
50+
// fallback if not matching a country condition
51+
createRedirect({
52+
fromPath: applyTrailingSlashOption(
53+
"/routes/redirect/country-condition",
54+
TRAILING_SLASH
55+
),
1456
toPath: applyTrailingSlashOption("/routes/redirect/hit", TRAILING_SLASH),
1557
})
1658

@@ -19,4 +61,4 @@ export const createPages: GatsbyNode["createPages"] = ({ actions: { createRedire
1961
component: path.resolve(`./src/components/footer.jsx`),
2062
context: {},
2163
})
22-
}
64+
}

e2e-tests/adapters/scripts/deploy-and-run/netlify.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ const deployInfo = JSON.parse(deployResults.stdout)
3232

3333
process.env.DEPLOY_URL = deployInfo.deploy_url
3434

35+
console.log(`Deployed to ${deployInfo.deploy_url}`)
36+
3537
try {
3638
await execa(`npm`, [`run`, npmScriptToRun], { stdio: `inherit` })
3739
} finally {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import * as React from "react"
2+
import Layout from "../../../components/layout"
3+
4+
const ExistingForcePage = () => {
5+
return (
6+
<Layout>
7+
<h1>Existing Force</h1>
8+
</Layout>
9+
)
10+
}
11+
12+
export default ExistingForcePage
13+
14+
export const Head = () => <title>Existing Force</title>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import * as React from "react"
2+
import Layout from "../../../components/layout"
3+
4+
const HitDEPage = () => {
5+
return (
6+
<Layout>
7+
<h1>Hit DE</h1>
8+
</Layout>
9+
)
10+
}
11+
12+
export default HitDEPage
13+
14+
export const Head = () => <title>Hit DE</title>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import * as React from "react"
2+
import Layout from "../../../components/layout"
3+
4+
const HitUSPage = () => {
5+
return (
6+
<Layout>
7+
<h1>Hit US</h1>
8+
</Layout>
9+
)
10+
}
11+
12+
export default HitUSPage
13+
14+
export const Head = () => <title>Hit US</title>

packages/gatsby-adapter-netlify/src/__tests__/route-handler.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import fs from "fs-extra"
22
import { tmpdir } from "os"
33
import { join } from "path"
4+
import type { IRedirectRoute, RoutesManifest } from "gatsby"
45
import {
56
injectEntries,
67
ADAPTER_MARKER_START,
78
ADAPTER_MARKER_END,
89
NETLIFY_PLUGIN_MARKER_START,
910
NETLIFY_PLUGIN_MARKER_END,
1011
GATSBY_PLUGIN_MARKER_START,
12+
processRoutesManifest,
1113
} from "../route-handler"
1214

1315
function generateLotOfContent(placeholderCharacter: string): string {
@@ -142,4 +144,50 @@ describe(`route-handler`, () => {
142144
})
143145
})
144146
})
147+
148+
describe(`createRedirects`, () => {
149+
it(`honors the force parameter`, async () => {
150+
const manifest: RoutesManifest = [
151+
{
152+
path: `/old-url`,
153+
type: `redirect`,
154+
toPath: `/new-url`,
155+
status: 301,
156+
headers: [{ key: `string`, value: `string` }],
157+
force: true,
158+
},
159+
{
160+
path: `/old-url2`,
161+
type: `redirect`,
162+
toPath: `/new-url2`,
163+
status: 308,
164+
headers: [{ key: `string`, value: `string` }],
165+
force: false,
166+
},
167+
]
168+
169+
const { redirects } = processRoutesManifest(manifest)
170+
171+
// `!` is appended to status to mark forced redirect
172+
expect(redirects).toMatch(/^\/old-url\s+\/new-url\s+301!$/m)
173+
// `!` is not appended to status to mark not forced redirect
174+
expect(redirects).toMatch(/^\/old-url2\s+\/new-url2\s+308$/m)
175+
})
176+
177+
it(`honors the conditions parameter`, async () => {
178+
const redirect: IRedirectRoute = {
179+
path: `/old-url`,
180+
type: `redirect`,
181+
toPath: `/new-url`,
182+
status: 200,
183+
headers: [{ key: `string`, value: `string` }],
184+
conditions: { language: [`ca`, `us`] },
185+
}
186+
187+
const { redirects } = processRoutesManifest([redirect])
188+
expect(redirects).toMatch(
189+
/^\/old-url\s+\/new-url\s+200\s+Language=ca,us$/m
190+
)
191+
})
192+
})
145193
})

0 commit comments

Comments
 (0)