Skip to content

Commit

Permalink
feat: environment level redirects
Browse files Browse the repository at this point in the history
  • Loading branch information
svedova committed May 16, 2024
1 parent 8936db6 commit 1569c07
Show file tree
Hide file tree
Showing 11 changed files with 475 additions and 51 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { RenderResult } from "@testing-library/react";
import { render } from "@testing-library/react";
import RedirectsEditor from "./RedirectsEditor";

interface WrapperProps {
value: string;
}

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/RedirectsEditor.tsx", () => {
let wrapper: RenderResult;
let onChange: jest.Func;

const createWrapper = ({ value }: WrapperProps) => {
onChange = jest.fn();

wrapper = render(<RedirectsEditor value={value} onChange={onChange} />);
};

test("should have a form", () => {
const value = "[ { 'from': '/', 'to': '/test' } ]";

createWrapper({ value });

expect(wrapper.getByText(value)).toBeTruthy();
expect(wrapper.getByText("docs").getAttribute("href")).toBe(
"https://stormkit.io/docs/features/redirects-and-path-rewrites"
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import CodeMirror from "@uiw/react-codemirror";
import { json } from "@codemirror/lang-json";
import Typography from "@mui/material/Typography";
import Link from "@mui/material/Link";
import { grey } from "@mui/material/colors";

interface Props {
value?: string;
onChange: (r: string) => void;
}

export default function RedirectsEditor({ value, onChange }: Props) {
return (
<>
<CodeMirror
maxHeight="200px"
value={value}
extensions={[json()]}
onChange={onChange}
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>
</>
);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
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";
Expand All @@ -11,13 +9,13 @@ 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";
import RedirectsEditor from "./RedirectsEditor";

interface Props {
env: Environment;
Expand Down Expand Up @@ -45,10 +43,10 @@ export default function RedirectsPlaygroundModal({
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 [redirects, setRedirects] = useState(exampleRedirects);

const submitHandler: FormEventHandler = e => {
e.preventDefault();
Expand Down Expand Up @@ -126,26 +124,7 @@ export default function RedirectsPlaygroundModal({
}}
>
<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>
<RedirectsEditor value={redirects} onChange={setRedirects} />
</Box>
{result &&
(result.match ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ export default function TabConfigGeneral({
color="secondary"
checked={autoPublish}
onChange={e => {
console.log("checked", e.target.checked);
setAutoPublish(e.target.checked);
}}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { RenderResult, waitFor } from "@testing-library/react";
import { fireEvent, render } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import mockApp from "~/testing/data/mock_app";
import mockEnvironments from "~/testing/data/mock_environments";
import { mockUpdateEnvironment } from "~/testing/nocks/nock_environment";
import TabConfigRedirects from "./TabConfigRedirects";

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/TabConfigRedirects.tsx", () => {
let wrapper: RenderResult;
let currentApp: App;
let currentEnv: Environment;
let setRefreshToken: jest.Func;

const createWrapper = ({ app, environment }: WrapperProps) => {
setRefreshToken = jest.fn();
currentApp = app || mockApp();
currentEnv = environment || mockEnvironments({ app: currentApp })[0];

wrapper = render(
<TabConfigRedirects
app={currentApp}
environment={currentEnv}
setRefreshToken={setRefreshToken}
/>
);
};

test("should have a form", () => {
createWrapper({});

expect(wrapper.getByText("Redirects")).toBeTruthy();
expect(
wrapper.getByText(
"Configure redirects and path rewrites for your application."
)
).toBeTruthy();

const redirectsFileInput = wrapper.getByLabelText(
"Redirects file location"
) as HTMLInputElement;

expect(redirectsFileInput.value).toBe("");

// Initially the docs should not be visible
expect(() => wrapper.getByText("docs")).toThrow();

expect(wrapper.getByText("Overwrite redirects")).toBeTruthy();

fireEvent.click(wrapper.getByText("Overwrite redirects"));

expect(wrapper.getByText("docs")).toBeTruthy();
});

test("should submit the form without redirects", async () => {
createWrapper({});

await userEvent.type(
wrapper.getByLabelText("Redirects file location"),
"/redirects.json"
);

const scope = mockUpdateEnvironment({
environment: {
...currentEnv,
build: {
...currentEnv.build,
previewLinks: true,
redirectsFile: "/redirects.json",
},
},
status: 200,
response: {
ok: true,
},
});

fireEvent.click(wrapper.getByText("Save"));

await waitFor(() => {
expect(scope.isDone()).toBe(true);
});
});

test("should submit the form with redirects", async () => {
createWrapper({});

await userEvent.type(
wrapper.getByLabelText("Redirects file location"),
"/redirects.json"
);

const scope = mockUpdateEnvironment({
environment: {
...currentEnv,
build: {
...currentEnv.build,
previewLinks: true,
redirects: [],
redirectsFile: "/redirects.json",
},
},
status: 200,
response: {
ok: true,
},
});

fireEvent.click(wrapper.getByLabelText("Overwrite redirects"));
fireEvent.click(wrapper.getByText("Save"));

await waitFor(() => {
expect(scope.isDone()).toBe(true);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,47 @@ import { useState } from "react";
import Box from "@mui/material/Box";
import TextField from "@mui/material/TextField";
import Button from "@mui/lab/LoadingButton";
import Switch from "@mui/material/Switch";
import FormControlLabel from "@mui/material/FormControlLabel";
import Typography from "@mui/material/Typography";
import { grey } from "@mui/material/colors";
import Card from "~/components/Card";
import CardHeader from "~/components/CardHeader";
import CardFooter from "~/components/CardFooter";
import { useSubmitHandler } from "../actions";
import RedirectsPlaygroundModal from "./RedirectsPlaygroundModal";
import RedirectsEditor from "./RedirectsEditor";

interface Props {
app: App;
environment: Environment;
setRefreshToken: (v: number) => void;
}

export default function TabConfigGeneral({
const defaultRedirects = `[
]`;

export default function TabConfigRedirects({
environment: env,
app,
setRefreshToken,
}: Props) {
const hasRedirects = Boolean(env.build.redirects);
const initialRedirects =
JSON.stringify(env.build.redirects, null, 2) || defaultRedirects;

const [showModal, setShowModal] = useState(false);
const [showRedirects, setShowRedirects] = useState(hasRedirects);
const [redirects, setRedirects] = useState(initialRedirects);

const { submitHandler, error, success, isLoading } = useSubmitHandler({
app,
env,
setRefreshToken,
controlled: {
"build.redirects": showRedirects ? redirects : undefined,
},
});

if (!env) {
Expand Down Expand Up @@ -69,6 +88,39 @@ export default function TabConfigGeneral({
}
/>
</Box>
<Box sx={{ bgcolor: "rgba(0,0,0,0.2)", p: 1.75, pt: 1, mb: 4 }}>
<FormControlLabel
sx={{ pl: 0, ml: 0 }}
label="Overwrite redirects"
control={
<Switch
color="secondary"
checked={showRedirects}
onChange={() => {
setShowRedirects(!showRedirects);
}}
/>
}
labelPlacement="start"
/>
<Typography sx={{ opacity: 0.5 }}>
Turn this switch on to overwrite redirects. These redirects will apply
to all deployments and will take precedence over redirects file.
</Typography>
</Box>
{showRedirects && (
<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>
<RedirectsEditor value={redirects} onChange={setRedirects} />
</Box>
)}

<CardFooter>
<Button
Expand Down
Loading

0 comments on commit 1569c07

Please sign in to comment.