Skip to content

Commit 8f695fd

Browse files
authored
Playwright test: Unmounting a fetcher cancels the request of an adjacent form (remix-run#5720)
1 parent 81cae8b commit 8f695fd

File tree

2 files changed

+105
-0
lines changed

2 files changed

+105
-0
lines changed

contributors.yml

+1
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,7 @@
513513
- zainfathoni
514514
- zayenz
515515
- zhe
516+
- oscarnewman
516517
- arifqys
517518
- iamzee
518519
- TimonVS

integration/fetcher-test.ts

+104
Original file line numberDiff line numberDiff line change
@@ -352,3 +352,107 @@ test.describe("useFetcher", () => {
352352
);
353353
});
354354
});
355+
356+
test.describe("fetcher aborts and adjacent forms", () => {
357+
let fixture: Fixture;
358+
let appFixture: AppFixture;
359+
360+
test.beforeAll(async () => {
361+
fixture = await createFixture({
362+
future: {
363+
v2_routeConvention: true,
364+
},
365+
files: {
366+
"app/routes/_index.jsx": js`
367+
import * as React from "react";
368+
import {
369+
Form,
370+
useFetcher,
371+
useLoaderData,
372+
useNavigation
373+
} from "@remix-run/react";
374+
375+
export async function loader({ request }) {
376+
// 1 second timeout on data
377+
await new Promise((r) => setTimeout(r, 1000));
378+
return { foo: 'bar' };
379+
}
380+
381+
export default function Index() {
382+
const [open, setOpen] = React.useState(true);
383+
const { data } = useLoaderData();
384+
const navigation = useNavigation();
385+
386+
return (
387+
<div>
388+
{navigation.state === 'idle' && <div id="idle">Idle</div>}
389+
<Form id="main-form">
390+
<input id="submit-form" type="submit" />
391+
</Form>
392+
393+
<button id="open" onClick={() => setOpen(true)}>Show async form</button>
394+
{open && <Child onClose={() => setOpen(false)} />}
395+
</div>
396+
);
397+
}
398+
399+
function Child({ onClose }) {
400+
const fetcher = useFetcher();
401+
402+
return (
403+
<fetcher.Form method="get" action="/api">
404+
<button id="submit-fetcher" type="submit">Trigger fetcher (shows a message)</button>
405+
<button
406+
type="submit"
407+
form="main-form"
408+
id="submit-and-close"
409+
onClick={() => setTimeout(onClose, 250)}
410+
>
411+
Submit main form and close async form
412+
</button>
413+
</fetcher.Form>
414+
);
415+
}
416+
`,
417+
418+
"app/routes/api.jsx": js`
419+
export async function loader() {
420+
await new Promise((resolve) => setTimeout(resolve, 500));
421+
return { message: 'Hello world!' }
422+
}
423+
`,
424+
},
425+
});
426+
427+
appFixture = await createAppFixture(fixture);
428+
});
429+
430+
test.afterAll(() => {
431+
appFixture.close();
432+
});
433+
434+
test("Unmounting a fetcher does not cancel the request of an adjacent form", async ({
435+
page,
436+
}) => {
437+
let app = new PlaywrightFixture(appFixture, page);
438+
await app.goto("/");
439+
440+
// Works as expected before the fetcher is loaded
441+
442+
// submit the main form and unmount the fetcher form
443+
await app.clickElement("#submit-and-close");
444+
// Wait for our navigation state to be "Idle"
445+
await page.waitForSelector("#idle", { timeout: 2000 });
446+
447+
// Breaks after the fetcher is loaded
448+
449+
// re-mount the fetcher form
450+
await app.clickElement("#open");
451+
// submit the fetcher form
452+
await app.clickElement("#submit-fetcher");
453+
// submit the main form and unmount the fetcher form
454+
await app.clickElement("#submit-and-close");
455+
// Wait for navigation state to be "Idle"
456+
await page.waitForSelector("#idle", { timeout: 2000 });
457+
});
458+
});

0 commit comments

Comments
 (0)