@@ -10013,11 +10013,41 @@ describe("a router", () => {
1001310013 });
1001410014 });
1001510015
10016+ describe(`
10017+ A) fetch GET /foo |--R
10018+ B) nav GET /bar |---O
10019+ `, () => {
10020+ it("ignores loader redirect navigation if preceded by a normal GET navigation", async () => {
10021+ let key = "key";
10022+ let t = initializeTmTest();
10023+
10024+ // Start a fetch load and interrupt with a navigation
10025+ let A = await t.fetch("/foo", key);
10026+ let B = await t.navigate("/bar");
10027+
10028+ // The fetcher loader redirect should be ignored
10029+ await A.loaders.foo.redirect("/baz");
10030+ expect(t.router.state.fetchers.get(key)?.state).toBe("idle");
10031+
10032+ await B.loaders.bar.resolve("BAR");
10033+ expect(t.router.state).toMatchObject({
10034+ navigation: IDLE_NAVIGATION,
10035+ location: { pathname: "/bar" },
10036+ loaderData: {
10037+ root: "ROOT",
10038+ bar: "BAR",
10039+ },
10040+ });
10041+ expect(t.router.state.fetchers.get(key)?.state).toBe("idle");
10042+ expect(t.router.state.fetchers.get(key)?.data).toBeUndefined();
10043+ });
10044+ });
10045+
1001610046 describe(`
1001710047 A) fetch POST /foo |--R
1001810048 B) nav GET /bar |---O
1001910049 `, () => {
10020- it("ignores submission redirect navigation if preceded by a normal navigation", async () => {
10050+ it("ignores submission redirect navigation if preceded by a normal GET navigation", async () => {
1002110051 let key = "key";
1002210052 let t = initializeTmTest();
1002310053 let A = await t.fetch("/foo", key, {
@@ -10046,16 +10076,20 @@ describe("a router", () => {
1004610076 });
1004710077
1004810078 describe(`
10049- A) fetch GET /foo |--R
10050- B) nav GET /bar |---O
10079+ A) fetch GET /foo |--R |---O
10080+ B) nav POST /bar |--|-- |---O
1005110081 `, () => {
10052- it("ignores loader redirect navigation if preceded by a normal navigation", async () => {
10082+ it("ignores loader redirect navigation if preceded by a normal POST navigation", async () => {
1005310083 let key = "key";
1005410084 let t = initializeTmTest();
1005510085
10056- // Start a fetch load and interrupt with a navigation
10086+ // Start a fetch load and interrupt with a POST navigation
1005710087 let A = await t.fetch("/foo", key);
10058- let B = await t.navigate("/bar", undefined, ["foo"]);
10088+ let B = await t.navigate(
10089+ "/bar",
10090+ { formMethod: "post", formData: createFormData({}) },
10091+ ["foo"]
10092+ );
1005910093
1006010094 // The fetcher loader redirect should be ignored
1006110095 await A.loaders.foo.redirect("/baz");
@@ -10064,28 +10098,35 @@ describe("a router", () => {
1006410098 // The navigation should trigger the fetcher to revalidate since it's
1006510099 // not yet "completed". If it returns data this time that should be
1006610100 // reflected
10101+ await B.actions.bar.resolve("ACTION");
10102+ await B.loaders.root.resolve("ROOT*");
1006710103 await B.loaders.bar.resolve("BAR");
1006810104 await B.loaders.foo.resolve("FOO");
1006910105
1007010106 expect(t.router.state).toMatchObject({
1007110107 navigation: IDLE_NAVIGATION,
1007210108 location: { pathname: "/bar" },
1007310109 loaderData: {
10074- root: "ROOT",
10110+ root: "ROOT* ",
1007510111 bar: "BAR",
1007610112 },
1007710113 });
1007810114 expect(t.router.state.fetchers.get(key)?.state).toBe("idle");
1007910115 expect(t.router.state.fetchers.get(key)?.data).toBe("FOO");
1008010116 });
1008110117
10082- it("processes second fetcher load redirect after interruption by normal navigation", async () => {
10118+ it("processes second fetcher load redirect after interruption by normal POST navigation", async () => {
1008310119 let key = "key";
1008410120 let t = initializeTmTest();
1008510121
10086- // Start a fetch load and interrupt with a navigation
10122+ // Start a fetch load and interrupt with a POST navigation
1008710123 let A = await t.fetch("/foo", key, "root");
10088- let B = await t.navigate("/bar", undefined, ["foo"]);
10124+ let B = await t.navigate(
10125+ "/bar",
10126+ { formMethod: "post", formData: createFormData({}) },
10127+ ["foo"]
10128+ );
10129+ expect(A.loaders.foo.signal.aborted).toBe(true);
1008910130
1009010131 // The fetcher loader redirect should be ignored
1009110132 await A.loaders.foo.redirect("/baz");
@@ -10097,6 +10138,8 @@ describe("a router", () => {
1009710138
1009810139 // The navigation should trigger the fetcher to revalidate since it's
1009910140 // not yet "completed". If it redirects again we should follow that
10141+ await B.actions.bar.resolve("ACTION");
10142+ await B.loaders.root.resolve("ROOT*");
1010010143 await B.loaders.bar.resolve("BAR");
1010110144 let C = await B.loaders.foo.redirect(
1010210145 "/foo/bar",
@@ -10114,20 +10157,26 @@ describe("a router", () => {
1011410157 expect(t.router.state.fetchers.get(key)?.state).toBe("loading");
1011510158
1011610159 // The fetcher should not revalidate here since it triggered the redirect
10160+ await C.loaders.root.resolve("ROOT**");
1011710161 await C.loaders.foobar.resolve("FOOBAR");
1011810162 expect(t.router.state).toMatchObject({
1011910163 navigation: IDLE_NAVIGATION,
1012010164 location: { pathname: "/foo/bar" },
1012110165 loaderData: {
10122- root: "ROOT",
10166+ root: "ROOT** ",
1012310167 foobar: "FOOBAR",
1012410168 },
1012510169 });
1012610170 expect(t.router.state.fetchers.get(key)?.state).toBe("idle");
1012710171 expect(t.router.state.fetchers.get(key)?.data).toBe(undefined);
1012810172 });
10173+ });
1012910174
10130- it("handle multiple fetcher loader redirects", async () => {
10175+ describe(`
10176+ A) fetch GET /foo |-----X
10177+ B) fetch GET /bar |---R
10178+ `, () => {
10179+ it("handles racing fetcher loader redirects", async () => {
1013110180 let keyA = "a";
1013210181 let keyB = "b";
1013310182 let t = initializeTmTest();
@@ -10136,11 +10185,8 @@ describe("a router", () => {
1013610185 let A = await t.fetch("/foo", keyA, "root");
1013710186 let B = await t.fetch("/bar", keyB, "root");
1013810187
10139- // Return a redirect from the second fetcher.load (which will trigger
10140- // a revalidation of the first fetcher)
10141- let C = await B.loaders.bar.redirect("/baz", undefined, undefined, [
10142- "foo",
10143- ]);
10188+ // Return a redirect from the second fetcher.load
10189+ let C = await B.loaders.bar.redirect("/baz");
1014410190 expect(t.router.state).toMatchObject({
1014510191 navigation: { location: { pathname: "/baz" } },
1014610192 location: { pathname: "/" },
@@ -10154,32 +10200,17 @@ describe("a router", () => {
1015410200 navigation: { location: { pathname: "/baz" } },
1015510201 location: { pathname: "/" },
1015610202 });
10157- expect(t.router.state.fetchers.get(keyA)?.state).toBe("loading ");
10203+ expect(t.router.state.fetchers.get(keyA)?.state).toBe("idle ");
1015810204 expect(t.router.state.fetchers.get(keyB)?.state).toBe("loading");
1015910205
10160- // Resolve the navigation loader and the revalidating (first) fetcher
10161- // loader which redirects again
10206+ // Resolve the navigation loader
1016210207 await C.loaders.baz.resolve("BAZ");
10163- let D = await C.loaders.foo.redirect("/foo/bar");
10164- expect(t.router.state).toMatchObject({
10165- navigation: { location: { pathname: "/foo/bar" } },
10166- location: { pathname: "/" },
10167- loaderData: {
10168- root: "ROOT",
10169- },
10170- });
10171- expect(t.router.state.fetchers.get(keyA)?.state).toBe("loading");
10172- expect(t.router.state.fetchers.get(keyB)?.state).toBe("loading");
10173-
10174- // Resolve the navigation loader, bringing everything back to idle at
10175- // the final location
10176- await D.loaders.foobar.resolve("FOOBAR");
1017710208 expect(t.router.state).toMatchObject({
1017810209 navigation: IDLE_NAVIGATION,
10179- location: { pathname: "/foo/bar " },
10210+ location: { pathname: "/baz " },
1018010211 loaderData: {
1018110212 root: "ROOT",
10182- foobar : "FOOBAR ",
10213+ baz : "BAZ ",
1018310214 },
1018410215 });
1018510216 expect(t.router.state.fetchers.get(keyA)?.state).toBe("idle");
@@ -11023,7 +11054,7 @@ describe("a router", () => {
1102311054 expect(t.router.state.fetchers.get(actionKey)).toBeUndefined();
1102411055 });
1102511056
11026- it("does not call shouldRevalidate if fetcher has no data (called 2x rapidly) ", async () => {
11057+ it("does not call shouldRevalidate on POST navigation if fetcher has not yet loaded ", async () => {
1102711058 // This is specifically for a Remix use case where the initial fetcher.load
1102811059 // call hasn't completed (and hasn't even loaded the route module yet), so
1102911060 // there isn't even a shouldRevalidate implementation to access yet. If
@@ -11040,7 +11071,9 @@ describe("a router", () => {
1104011071 index: true,
1104111072 },
1104211073 {
11074+ id: "page",
1104311075 path: "page",
11076+ action: true,
1104411077 },
1104511078 ],
1104611079 },
@@ -11056,11 +11089,15 @@ describe("a router", () => {
1105611089 let key = "key";
1105711090 let A = await t.fetch("/fetch", key, "root");
1105811091 expect(t.router.state.fetchers.get(key)?.state).toBe("loading");
11059- expect(A.loaders.fetch.signal.aborted).toBe(false);
1106011092
1106111093 // This should trigger an automatic revalidation of the fetcher since it
1106211094 // hasn't loaded yet
11063- let B = await t.navigate("/page", undefined, ["fetch"]);
11095+ let B = await t.navigate(
11096+ "/page",
11097+ { formMethod: "post", body: createFormData({}) },
11098+ ["fetch"]
11099+ );
11100+ await B.actions.page.resolve("ACTION");
1106411101 expect(t.router.state.fetchers.get(key)?.state).toBe("loading");
1106511102 expect(A.loaders.fetch.signal.aborted).toBe(true);
1106611103 expect(B.loaders.fetch.signal.aborted).toBe(false);
@@ -11078,6 +11115,54 @@ describe("a router", () => {
1107811115 });
1107911116 expect(spy).not.toHaveBeenCalled();
1108011117 });
11118+
11119+ it("does not trigger revalidation on GET navigation if fetcher has not yet loaded", async () => {
11120+ let spy = jest.fn(() => true);
11121+ let t = setup({
11122+ routes: [
11123+ {
11124+ id: "root",
11125+ path: "/",
11126+ children: [
11127+ {
11128+ index: true,
11129+ },
11130+ {
11131+ id: "page",
11132+ path: "page",
11133+ loader: true,
11134+ },
11135+ ],
11136+ },
11137+ {
11138+ id: "fetch",
11139+ path: "/fetch",
11140+ loader: true,
11141+ shouldRevalidate: spy,
11142+ },
11143+ ],
11144+ });
11145+
11146+ let key = "key";
11147+ let A = await t.fetch("/fetch", key, "root");
11148+ expect(t.router.state.fetchers.get(key)?.state).toBe("loading");
11149+
11150+ let B = await t.navigate("/page");
11151+ expect(t.router.state.fetchers.get(key)?.state).toBe("loading");
11152+ expect(A.loaders.fetch.signal.aborted).toBe(false);
11153+
11154+ await A.loaders.fetch.resolve("A");
11155+ expect(t.router.state.fetchers.get(key)?.state).toBe("idle");
11156+
11157+ // Complete the navigation
11158+ await B.loaders.page.resolve("PAGE");
11159+ expect(t.router.state.navigation.state).toBe("idle");
11160+ expect(t.router.state.fetchers.get(key)).toMatchObject({
11161+ state: "idle",
11162+ data: "A",
11163+ });
11164+ expect(spy).not.toHaveBeenCalled();
11165+ });
1108111166 });
1108211167
1108311168 describe("fetcher ?index params", () => {
0 commit comments