-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
336 additions
and
0 deletions.
There are no files selected for viewing
87 changes: 87 additions & 0 deletions
87
...ages/apps/[id]/environments/[env-id]/config/_components/RedirectsPlaygroundModal.spec.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import { RenderResult, waitFor } from "@testing-library/react"; | ||
import { fireEvent, render } from "@testing-library/react"; | ||
import mockApp from "~/testing/data/mock_app"; | ||
import mockEnvironments from "~/testing/data/mock_environments"; | ||
import { mockPlayground } from "~/testing/nocks/nock_redirects_playground"; | ||
import RedirectsPlaygroundModal from "./RedirectsPlaygroundModal"; | ||
|
||
interface WrapperProps { | ||
app?: App; | ||
environment?: Environment; | ||
setRefreshToken?: () => void; | ||
} | ||
|
||
jest.mock("@codemirror/lang-json", () => ({ json: jest.fn() })); | ||
jest.mock("@uiw/react-codemirror", () => ({ value }: { value: string }) => ( | ||
<>{value}</> | ||
)); | ||
|
||
describe("~/pages/apps/[id]/environments/[env-id]/config/_components/RedirectsPlaygroundModal.tsx", () => { | ||
let wrapper: RenderResult; | ||
let currentApp: App; | ||
let currentEnv: Environment; | ||
let closeSpy: jest.Func; | ||
|
||
const createWrapper = ({ app, environment }: WrapperProps) => { | ||
closeSpy = jest.fn(); | ||
currentApp = app || mockApp(); | ||
currentEnv = environment || mockEnvironments({ app: currentApp })[0]; | ||
|
||
wrapper = render( | ||
<RedirectsPlaygroundModal | ||
appId={currentApp.id} | ||
env={currentEnv} | ||
onClose={closeSpy} | ||
/> | ||
); | ||
}; | ||
|
||
test("should have a form", () => { | ||
createWrapper({}); | ||
|
||
expect(wrapper.getByText("Redirects Playground")).toBeTruthy(); | ||
|
||
const requestUrlInput = wrapper.getByLabelText( | ||
"Request URL" | ||
) as HTMLInputElement; | ||
|
||
expect(requestUrlInput.value).toBe("https://app.stormkit.io"); | ||
|
||
expect( | ||
wrapper.getByText("Provide a URL to test against the redirects.") | ||
).toBeTruthy(); | ||
|
||
expect(wrapper.getByText("docs").getAttribute("href")).toBe( | ||
"https://stormkit.io/docs/features/redirects-and-path-rewrites" | ||
); | ||
}); | ||
|
||
test("should submit the form", async () => { | ||
createWrapper({}); | ||
|
||
const scope = mockPlayground({ | ||
appId: currentApp.id, | ||
envId: currentEnv.id!, | ||
address: "https://app.stormkit.io", | ||
redirects: [{ from: "/my-path", to: "/my-new-path", status: 302 }], | ||
response: { | ||
match: true, | ||
proxy: false, | ||
status: 302, | ||
redirect: "https://app.stormkit.io/new-url", | ||
rewrite: "", | ||
}, | ||
}); | ||
|
||
fireEvent.submit(wrapper.getByTestId("redirects-playground-form")); | ||
|
||
await waitFor(() => { | ||
expect(scope.isDone()).toBe(true); | ||
expect(wrapper.getByText("It's a match!")); | ||
expect(wrapper.getByText("Redirect 302")); | ||
expect(wrapper.getByText("https://app.stormkit.io/new-url")); | ||
expect(wrapper.getByText("Should proxy?")); | ||
expect(wrapper.getByText("No")); | ||
}); | ||
}); | ||
}); |
198 changes: 198 additions & 0 deletions
198
src/pages/apps/[id]/environments/[env-id]/config/_components/RedirectsPlaygroundModal.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
import { FormEventHandler, useState } from "react"; | ||
import CodeMirror from "@uiw/react-codemirror"; | ||
import { json } from "@codemirror/lang-json"; | ||
import Table from "@mui/material/Table"; | ||
import TableRow from "@mui/material/TableRow"; | ||
import TableCell from "@mui/material/TableCell"; | ||
import TableBody from "@mui/material/TableBody"; | ||
import Alert from "@mui/material/Alert"; | ||
import AlertTitle from "@mui/material/AlertTitle"; | ||
import Button from "@mui/lab/LoadingButton"; | ||
import TextInput from "@mui/material/TextField"; | ||
import Typography from "@mui/material/Typography"; | ||
import Box from "@mui/material/Box"; | ||
import Link from "@mui/material/Link"; | ||
import Modal from "~/components/Modal"; | ||
import Card from "~/components/Card"; | ||
import CardHeader from "~/components/CardHeader"; | ||
import CardFooter from "~/components/CardFooter"; | ||
import api from "~/utils/api/Api"; | ||
import { grey } from "@mui/material/colors"; | ||
|
||
interface Props { | ||
env: Environment; | ||
appId: string; | ||
onClose: () => void; | ||
} | ||
|
||
interface SubmitReturn { | ||
match: boolean; | ||
against: string; | ||
pattern: string; | ||
rewrite: string; | ||
redirect: string; | ||
proxy: boolean; | ||
status: number; | ||
} | ||
|
||
const exampleRedirects = `[ | ||
{ "from": "/my-path", "to": "/my-new-path", "status": 302 } | ||
]`; | ||
|
||
export default function RedirectsPlaygroundModal({ | ||
onClose, | ||
appId, | ||
env, | ||
}: Props) { | ||
const [address, setAddress] = useState(env.preview); | ||
const [redirects, setRedirects] = useState(exampleRedirects); | ||
const [result, setResult] = useState<SubmitReturn>(); | ||
const [error, setError] = useState<string>(); | ||
const [loading, setLoading] = useState(false); | ||
|
||
const submitHandler: FormEventHandler = e => { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
|
||
let parsed: any; | ||
|
||
try { | ||
parsed = JSON.parse(redirects); | ||
} catch { | ||
setError("Invalid JSON format."); | ||
return; | ||
} | ||
|
||
if (!address.trim()) { | ||
setError("Request URL cannot be empty."); | ||
return; | ||
} | ||
|
||
setLoading(true); | ||
|
||
api | ||
.post<SubmitReturn>("/redirects/playground", { | ||
appId: appId, | ||
envId: env.id, | ||
address, | ||
redirects: parsed, | ||
}) | ||
.then(match => { | ||
setResult(match); | ||
}) | ||
.finally(() => { | ||
setError(undefined); | ||
setLoading(false); | ||
}); | ||
}; | ||
|
||
return ( | ||
<Modal open onClose={onClose} maxWidth="lg"> | ||
<Card | ||
data-testid="redirects-playground-form" | ||
component="form" | ||
onSubmit={submitHandler} | ||
error={ | ||
error || !env.published | ||
? "Redirects are tested against the published deployments. Please deploy and publish before testing redirects." | ||
: undefined | ||
} | ||
> | ||
<CardHeader title="Redirects Playground" /> | ||
<Box sx={{ mb: 4 }}> | ||
<TextInput | ||
variant="filled" | ||
label="Request URL" | ||
placeholder="e.g. https://www.stormkit.io/docs" | ||
fullWidth | ||
autoFocus | ||
autoComplete="off" | ||
value={address} | ||
onChange={e => setAddress(e.target.value)} | ||
helperText={ | ||
<Typography sx={{ mt: 1 }} component="span"> | ||
Provide a URL to test against the redirects. | ||
</Typography> | ||
} | ||
/> | ||
</Box> | ||
<Box | ||
sx={{ | ||
mb: 4, | ||
p: 2, | ||
bgcolor: "rgba(0,0,0,0.2)", | ||
borderBottom: `1px solid ${grey[900]}`, | ||
}} | ||
> | ||
<Typography sx={{ mb: 2, color: grey[400] }}>Redirects</Typography> | ||
<CodeMirror | ||
maxHeight="200px" | ||
value={redirects} | ||
extensions={[json()]} | ||
onChange={value => setRedirects(value)} | ||
theme="dark" | ||
/> | ||
<Typography sx={{ mt: 2, color: grey[400] }}> | ||
Check the{" "} | ||
<Link | ||
href={ | ||
"https://stormkit.io/docs/features/redirects-and-path-rewrites" | ||
} | ||
target="_blank" | ||
rel="noreferrer noopener" | ||
> | ||
docs | ||
</Link>{" "} | ||
for more information. | ||
</Typography> | ||
</Box> | ||
{result && | ||
(result.match ? ( | ||
<> | ||
<Alert color="success" sx={{ mb: 0 }}> | ||
<Typography>It's a match!</Typography> | ||
</Alert> | ||
<Table sx={{ mb: 4 }}> | ||
<TableBody> | ||
<TableRow> | ||
<TableCell>Status</TableCell> | ||
<TableCell> | ||
{result.status | ||
? `Redirect ${result.status}` | ||
: "Path Rewrite"} | ||
</TableCell> | ||
</TableRow> | ||
<TableRow> | ||
<TableCell>To</TableCell> | ||
<TableCell>{result.redirect || result.rewrite}</TableCell> | ||
</TableRow> | ||
<TableRow> | ||
<TableCell>Should proxy?</TableCell> | ||
<TableCell>{result.proxy ? "Yes" : "No"}</TableCell> | ||
</TableRow> | ||
</TableBody> | ||
</Table> | ||
</> | ||
) : ( | ||
<Alert color="error" sx={{ mb: 4 }}> | ||
<AlertTitle>Not a match</AlertTitle> | ||
<Typography> | ||
Provided domain did not match any redirect rule. | ||
</Typography> | ||
</Alert> | ||
))} | ||
<CardFooter> | ||
<Button | ||
variant="contained" | ||
color="secondary" | ||
type="submit" | ||
disabled={!env.published} | ||
loading={loading} | ||
> | ||
Test | ||
</Button> | ||
</CardFooter> | ||
</Card> | ||
</Modal> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import nock from "nock"; | ||
|
||
const endpoint = process.env.API_DOMAIN || ""; | ||
|
||
interface PlaygroundProps { | ||
appId: string; | ||
envId: string; | ||
redirects: any; | ||
address: string; | ||
status?: number; | ||
response: { | ||
match: boolean; | ||
status: number; | ||
redirect: string; | ||
rewrite: string; | ||
proxy: boolean; | ||
}; | ||
} | ||
|
||
export const mockPlayground = ({ | ||
appId, | ||
envId, | ||
address, | ||
redirects, | ||
response, | ||
status = 200, | ||
}: PlaygroundProps) => | ||
nock(endpoint) | ||
.post("/redirects/playground", { appId, envId, redirects, address }) | ||
.reply(status, response); |