@@ -10013,11 +10013,41 @@ describe("a router", () => {
10013
10013
});
10014
10014
});
10015
10015
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
+
10016
10046
describe(`
10017
10047
A) fetch POST /foo |--R
10018
10048
B) nav GET /bar |---O
10019
10049
`, () => {
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 () => {
10021
10051
let key = "key";
10022
10052
let t = initializeTmTest();
10023
10053
let A = await t.fetch("/foo", key, {
@@ -10046,16 +10076,20 @@ describe("a router", () => {
10046
10076
});
10047
10077
10048
10078
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
10051
10081
`, () => {
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 () => {
10053
10083
let key = "key";
10054
10084
let t = initializeTmTest();
10055
10085
10056
- // Start a fetch load and interrupt with a navigation
10086
+ // Start a fetch load and interrupt with a POST navigation
10057
10087
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
+ );
10059
10093
10060
10094
// The fetcher loader redirect should be ignored
10061
10095
await A.loaders.foo.redirect("/baz");
@@ -10064,28 +10098,35 @@ describe("a router", () => {
10064
10098
// The navigation should trigger the fetcher to revalidate since it's
10065
10099
// not yet "completed". If it returns data this time that should be
10066
10100
// reflected
10101
+ await B.actions.bar.resolve("ACTION");
10102
+ await B.loaders.root.resolve("ROOT*");
10067
10103
await B.loaders.bar.resolve("BAR");
10068
10104
await B.loaders.foo.resolve("FOO");
10069
10105
10070
10106
expect(t.router.state).toMatchObject({
10071
10107
navigation: IDLE_NAVIGATION,
10072
10108
location: { pathname: "/bar" },
10073
10109
loaderData: {
10074
- root: "ROOT",
10110
+ root: "ROOT* ",
10075
10111
bar: "BAR",
10076
10112
},
10077
10113
});
10078
10114
expect(t.router.state.fetchers.get(key)?.state).toBe("idle");
10079
10115
expect(t.router.state.fetchers.get(key)?.data).toBe("FOO");
10080
10116
});
10081
10117
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 () => {
10083
10119
let key = "key";
10084
10120
let t = initializeTmTest();
10085
10121
10086
- // Start a fetch load and interrupt with a navigation
10122
+ // Start a fetch load and interrupt with a POST navigation
10087
10123
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);
10089
10130
10090
10131
// The fetcher loader redirect should be ignored
10091
10132
await A.loaders.foo.redirect("/baz");
@@ -10097,6 +10138,8 @@ describe("a router", () => {
10097
10138
10098
10139
// The navigation should trigger the fetcher to revalidate since it's
10099
10140
// 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*");
10100
10143
await B.loaders.bar.resolve("BAR");
10101
10144
let C = await B.loaders.foo.redirect(
10102
10145
"/foo/bar",
@@ -10114,20 +10157,26 @@ describe("a router", () => {
10114
10157
expect(t.router.state.fetchers.get(key)?.state).toBe("loading");
10115
10158
10116
10159
// The fetcher should not revalidate here since it triggered the redirect
10160
+ await C.loaders.root.resolve("ROOT**");
10117
10161
await C.loaders.foobar.resolve("FOOBAR");
10118
10162
expect(t.router.state).toMatchObject({
10119
10163
navigation: IDLE_NAVIGATION,
10120
10164
location: { pathname: "/foo/bar" },
10121
10165
loaderData: {
10122
- root: "ROOT",
10166
+ root: "ROOT** ",
10123
10167
foobar: "FOOBAR",
10124
10168
},
10125
10169
});
10126
10170
expect(t.router.state.fetchers.get(key)?.state).toBe("idle");
10127
10171
expect(t.router.state.fetchers.get(key)?.data).toBe(undefined);
10128
10172
});
10173
+ });
10129
10174
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 () => {
10131
10180
let keyA = "a";
10132
10181
let keyB = "b";
10133
10182
let t = initializeTmTest();
@@ -10136,11 +10185,8 @@ describe("a router", () => {
10136
10185
let A = await t.fetch("/foo", keyA, "root");
10137
10186
let B = await t.fetch("/bar", keyB, "root");
10138
10187
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");
10144
10190
expect(t.router.state).toMatchObject({
10145
10191
navigation: { location: { pathname: "/baz" } },
10146
10192
location: { pathname: "/" },
@@ -10154,32 +10200,17 @@ describe("a router", () => {
10154
10200
navigation: { location: { pathname: "/baz" } },
10155
10201
location: { pathname: "/" },
10156
10202
});
10157
- expect(t.router.state.fetchers.get(keyA)?.state).toBe("loading ");
10203
+ expect(t.router.state.fetchers.get(keyA)?.state).toBe("idle ");
10158
10204
expect(t.router.state.fetchers.get(keyB)?.state).toBe("loading");
10159
10205
10160
- // Resolve the navigation loader and the revalidating (first) fetcher
10161
- // loader which redirects again
10206
+ // Resolve the navigation loader
10162
10207
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");
10177
10208
expect(t.router.state).toMatchObject({
10178
10209
navigation: IDLE_NAVIGATION,
10179
- location: { pathname: "/foo/bar " },
10210
+ location: { pathname: "/baz " },
10180
10211
loaderData: {
10181
10212
root: "ROOT",
10182
- foobar : "FOOBAR ",
10213
+ baz : "BAZ ",
10183
10214
},
10184
10215
});
10185
10216
expect(t.router.state.fetchers.get(keyA)?.state).toBe("idle");
@@ -11023,7 +11054,7 @@ describe("a router", () => {
11023
11054
expect(t.router.state.fetchers.get(actionKey)).toBeUndefined();
11024
11055
});
11025
11056
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 () => {
11027
11058
// This is specifically for a Remix use case where the initial fetcher.load
11028
11059
// call hasn't completed (and hasn't even loaded the route module yet), so
11029
11060
// there isn't even a shouldRevalidate implementation to access yet. If
@@ -11040,7 +11071,9 @@ describe("a router", () => {
11040
11071
index: true,
11041
11072
},
11042
11073
{
11074
+ id: "page",
11043
11075
path: "page",
11076
+ action: true,
11044
11077
},
11045
11078
],
11046
11079
},
@@ -11056,11 +11089,15 @@ describe("a router", () => {
11056
11089
let key = "key";
11057
11090
let A = await t.fetch("/fetch", key, "root");
11058
11091
expect(t.router.state.fetchers.get(key)?.state).toBe("loading");
11059
- expect(A.loaders.fetch.signal.aborted).toBe(false);
11060
11092
11061
11093
// This should trigger an automatic revalidation of the fetcher since it
11062
11094
// 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");
11064
11101
expect(t.router.state.fetchers.get(key)?.state).toBe("loading");
11065
11102
expect(A.loaders.fetch.signal.aborted).toBe(true);
11066
11103
expect(B.loaders.fetch.signal.aborted).toBe(false);
@@ -11078,6 +11115,54 @@ describe("a router", () => {
11078
11115
});
11079
11116
expect(spy).not.toHaveBeenCalled();
11080
11117
});
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
+ });
11081
11166
});
11082
11167
11083
11168
describe("fetcher ?index params", () => {
0 commit comments