Skip to content

Commit

Permalink
Updates to back-compat for useFetcher/useFetchers
Browse files Browse the repository at this point in the history
  • Loading branch information
brophdawg11 committed Dec 8, 2022
1 parent bca3696 commit ee8ad35
Show file tree
Hide file tree
Showing 4 changed files with 412 additions and 75 deletions.
270 changes: 270 additions & 0 deletions integration/fetcher-state-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
import { test, expect } from "@playwright/test";
import { ServerMode } from "@remix-run/server-runtime/mode";

import { createAppFixture, createFixture, js } from "./helpers/create-fixture";
import type { Fixture, AppFixture } from "./helpers/create-fixture";
import { PlaywrightFixture } from "./helpers/playwright-fixture";

// idle, done, actionReload are tested during the testing of these flows
const TYPES = {
actionSubmission: "actionSubmission",
loaderSubmission: "loaderSubmission",
actionRedirect: "actionRedirect",
normalLoad: "normalLoad",
};

test.describe("fetcher states", () => {
let fixture: Fixture;
let appFixture: AppFixture;

test.beforeAll(async () => {
fixture = await createFixture({
files: {
"app/root.jsx": js`
import { useMemo, useRef } from "react";
import { Outlet, Scripts, useFetchers } from "@remix-run/react";
export default function Comp() {
// Only gonna use a single fetcher in any given test but this way
// we can route away from the child route and preserve the info
const [fetcher] = useFetchers();
const fetcherRef = useRef();
const states = useMemo(() => {
if (!fetcher) return
const savedStates = fetcherRef.current || [];
savedStates.push({
state: fetcher.state,
type: fetcher.type,
submission: fetcher.submission ? {
...fetcher.submission,
formData: Object.fromEntries(fetcher.submission.formData.entries()),
key: undefined
}: undefined,
data: fetcher.data,
});
fetcherRef.current = savedStates;
return savedStates;
}, [fetcher]);
return (
<html lang="en">
<body>
<Outlet />
{fetcher && fetcher.state != "idle" && (
<p id="loading">Loading...</p>
)}
<p>
<code id="states">
{JSON.stringify(states, null, 2)}
</code>
</p>
<Scripts />
</body>
</html>
);
}
`,
"app/routes/page.jsx": js`
import { redirect } from "@remix-run/node";
import { useFetcher } from "@remix-run/react";
export function loader() {
return { from: 'loader' }
}
export async function action({ request }) {
let fd = await request.formData()
if (fd.has('redirect')) {
return redirect('/redirect');
}
return { from: 'action' }
}
export default function() {
const fetcher = useFetcher();
return (
<>
{fetcher.type === 'init' ?
<pre id="initial-state">
{
JSON.stringify({
state: fetcher.state,
type: fetcher.type,
submission: fetcher.submission,
data: fetcher.data,
})
}
</pre> :
null}
<fetcher.Form method="post">
${TYPES.actionSubmission}
<button type="submit" name="key" value="value" id="${TYPES.actionSubmission}">
Submit ${TYPES.actionSubmission}
</button>
</fetcher.Form>
<fetcher.Form method="get">
${TYPES.loaderSubmission}
<button type="submit" name="key" value="value" id="${TYPES.loaderSubmission}">
Submit ${TYPES.loaderSubmission}
</button>
</fetcher.Form>
<fetcher.Form method="post">
${TYPES.actionRedirect}
<button type="submit" name="redirect" value="yes" id="${TYPES.actionRedirect}">
Submit ${TYPES.actionRedirect}
</button>
</fetcher.Form>
<button id="${TYPES.normalLoad}" onClick={() => fetcher.load('/page')}>
Submit ${TYPES.normalLoad}
</button>
</>
);
}
`,
"app/routes/redirect.jsx": js`
import { useFetcher } from "@remix-run/react";
export function loader() {
return { from: 'redirect loader' }
}
export default function() {
return <h1>Redirect</h1>;
}
`,
},
});

appFixture = await createAppFixture(fixture, ServerMode.Development);
});

test.afterAll(async () => {
await appFixture.close();
});

test("represents a initial fetcher", async ({ page }) => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/page", true);
let text = (await app.getElement("#initial-state")).text();
expect(JSON.parse(text)).toEqual({
state: "idle",
type: "init",
});
});

test("represents an actionSubmission fetcher", async ({ page }) => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/page", true);
await app.clickElement(`#${TYPES.actionSubmission}`);
await page.waitForSelector("#loading", { state: "hidden" });
let text = (await app.getElement("#states")).text();
expect(JSON.parse(text)).toEqual([
{
state: "submitting",
type: "actionSubmission",
submission: {
formData: { key: "value" },
action: "/page",
method: "POST",
encType: "application/x-www-form-urlencoded",
},
},
{
state: "loading",
type: "actionReload",
submission: {
formData: { key: "value" },
action: "/page",
method: "POST",
encType: "application/x-www-form-urlencoded",
},
data: {
from: "action",
},
},
{
state: "idle",
type: "done",
data: {
from: "action",
},
},
]);
});

test("represents a loaderSubmission fetcher", async ({ page }) => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/page", true);
await app.clickElement(`#${TYPES.loaderSubmission}`);
await page.waitForSelector("#loading", { state: "hidden" });
let text = (await app.getElement("#states")).text();
expect(JSON.parse(text)).toEqual([
{
state: "submitting",
type: "loaderSubmission",
submission: {
formData: { key: "value" },
// TODO: I think this is a bug and will be fixed with RR 6.4
action: "/page?key=value",
method: "GET",
encType: "application/x-www-form-urlencoded",
},
},
{
state: "idle",
type: "done",
data: {
from: "loader",
},
},
]);
});

test("represents an actionRedirect fetcher", async ({ page }) => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/page", true);
await app.clickElement(`#${TYPES.actionRedirect}`);
await page.waitForSelector("#loading", { state: "hidden" });
let text = (await app.getElement("#states")).text();
expect(JSON.parse(text)).toEqual([
{
state: "submitting",
type: "actionSubmission",
submission: {
formData: { redirect: "yes" },
action: "/page",
method: "POST",
encType: "application/x-www-form-urlencoded",
},
},
{
state: "loading",
type: "actionRedirect",
submission: {
formData: { redirect: "yes" },
action: "/page",
method: "POST",
encType: "application/x-www-form-urlencoded",
},
},
{
state: "idle",
type: "done",
},
]);
});

test("represents a normalLoad fetcher", async ({ page }) => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/page", true);
await app.clickElement(`#${TYPES.normalLoad}`);
await page.waitForSelector("#loading", { state: "hidden" });
let text = (await app.getElement("#states")).text();
expect(JSON.parse(text)).toEqual([
{
state: "loading",
type: "normalLoad",
},
{
data: { from: "loader" },
state: "idle",
type: "done",
},
]);
});
});
11 changes: 10 additions & 1 deletion packages/remix-eslint-config/rules/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,16 @@ module.exports = {
"no-new-object": WARN,
"no-octal": WARN,
"no-redeclare": ERROR,
"no-restricted-imports": [WARN, ...replaceRemixImportsOptions],
"no-restricted-imports": [
WARN,
...replaceRemixImportsOptions,
{
importNames: ["useTransition"],
message:
"useTransition is deprecated in favor of useNavigation as of v1.9.0.",
name: "@remix-run/react",
},
],
"no-script-url": WARN,
"no-self-assign": WARN,
"no-self-compare": WARN,
Expand Down

0 comments on commit ee8ad35

Please sign in to comment.