@@ -113,6 +113,97 @@ test.describe("Middleware", () => {
113
113
appFixture . close ( ) ;
114
114
} ) ;
115
115
116
+ test ( "calls clientMiddleware before/after loaders with split route modules" , async ( {
117
+ page,
118
+ } ) => {
119
+ let fixture = await createFixture ( {
120
+ spaMode : true ,
121
+ files : {
122
+ "react-router.config.ts" : reactRouterConfig ( {
123
+ ssr : false ,
124
+ middleware : true ,
125
+ splitRouteModules : true ,
126
+ } ) ,
127
+ "vite.config.ts" : js `
128
+ import { defineConfig } from "vite";
129
+ import { reactRouter } from "@react-router/dev/vite";
130
+
131
+ export default defineConfig({
132
+ build: { manifest: true, minify: false },
133
+ plugins: [reactRouter()],
134
+ });
135
+ ` ,
136
+ "app/context.ts" : js `
137
+ import { unstable_createContext } from 'react-router'
138
+ export const orderContext = unstable_createContext([]);
139
+ ` ,
140
+ "app/routes/_index.tsx" : js `
141
+ import { Link } from 'react-router'
142
+ import { orderContext } from '../context'
143
+
144
+ export const unstable_clientMiddleware = [
145
+ ({ context }) => {
146
+ context.set(orderContext, [...context.get(orderContext), 'a']);
147
+ },
148
+ ({ context }) => {
149
+ context.set(orderContext, [...context.get(orderContext), 'b']);
150
+ },
151
+ ];
152
+
153
+ export async function clientLoader({ request, context }) {
154
+ return context.get(orderContext).join(',');
155
+ }
156
+
157
+ export default function Component({ loaderData }) {
158
+ return (
159
+ <>
160
+ <h2 data-route>Index: {loaderData}</h2>
161
+ <Link to="/about">Go to about</Link>
162
+ </>
163
+ );
164
+ }
165
+ ` ,
166
+ "app/routes/about.tsx" : js `
167
+ import { orderContext } from '../context'
168
+
169
+ export const unstable_clientMiddleware = [
170
+ ({ context }) => {
171
+ context.set(orderContext, [...context.get(orderContext), 'c']);
172
+ },
173
+ ({ context }) => {
174
+ context.set(orderContext, [...context.get(orderContext), 'd']);
175
+ },
176
+ ];
177
+
178
+ export async function clientLoader({ context }) {
179
+ return context.get(orderContext).join(',');
180
+ }
181
+
182
+ export default function Component({ loaderData }) {
183
+ return <h2 data-route>About: {loaderData}</h2>;
184
+ }
185
+ ` ,
186
+ } ,
187
+ } ) ;
188
+
189
+ let appFixture = await createAppFixture ( fixture ) ;
190
+
191
+ let app = new PlaywrightFixture ( appFixture , page ) ;
192
+ await app . goto ( "/" ) ;
193
+ await page . waitForSelector ( '[data-route]:has-text("Index")' ) ;
194
+ expect ( await page . locator ( "[data-route]" ) . textContent ( ) ) . toBe (
195
+ "Index: a,b"
196
+ ) ;
197
+
198
+ ( await page . $ ( 'a[href="/about"]' ) ) ?. click ( ) ;
199
+ await page . waitForSelector ( '[data-route]:has-text("About")' ) ;
200
+ expect ( await page . locator ( "[data-route]" ) . textContent ( ) ) . toBe (
201
+ "About: c,d"
202
+ ) ;
203
+
204
+ appFixture . close ( ) ;
205
+ } ) ;
206
+
116
207
test ( "calls clientMiddleware before/after actions" , async ( { page } ) => {
117
208
let fixture = await createFixture ( {
118
209
spaMode : true ,
@@ -596,6 +687,94 @@ test.describe("Middleware", () => {
596
687
appFixture . close ( ) ;
597
688
} ) ;
598
689
690
+ test ( "calls clientMiddleware before/after loaders with split route modules" , async ( {
691
+ page,
692
+ } ) => {
693
+ let fixture = await createFixture ( {
694
+ files : {
695
+ "react-router.config.ts" : reactRouterConfig ( {
696
+ middleware : true ,
697
+ splitRouteModules : true ,
698
+ } ) ,
699
+ "vite.config.ts" : js `
700
+ import { defineConfig } from "vite";
701
+ import { reactRouter } from "@react-router/dev/vite";
702
+
703
+ export default defineConfig({
704
+ build: { manifest: true, minify: false },
705
+ plugins: [reactRouter()],
706
+ });
707
+ ` ,
708
+ "app/context.ts" : js `
709
+ import { unstable_createContext } from 'react-router'
710
+ export const orderContext = unstable_createContext([]);
711
+ ` ,
712
+ "app/routes/_index.tsx" : js `
713
+ import { Link } from 'react-router'
714
+ import { orderContext } from "../context";;
715
+
716
+ export const unstable_clientMiddleware = [
717
+ ({ context }) => {
718
+ context.set(orderContext, [...context.get(orderContext), 'a']);
719
+ },
720
+ ({ context }) => {
721
+ context.set(orderContext, [...context.get(orderContext), 'b']);
722
+ },
723
+ ];
724
+
725
+ export async function clientLoader({ request, context }) {
726
+ return context.get(orderContext).join(',');
727
+ }
728
+
729
+ export default function Component({ loaderData }) {
730
+ return (
731
+ <>
732
+ <h2 data-route>Index: {loaderData}</h2>
733
+ <Link to="/about">Go to about</Link>
734
+ </>
735
+ );
736
+ }
737
+ ` ,
738
+ "app/routes/about.tsx" : js `
739
+ import { orderContext } from "../context";;
740
+ export const unstable_clientMiddleware = [
741
+ ({ context }) => {
742
+ context.set(orderContext, ['c']); // reset order from hydration
743
+ },
744
+ ({ context }) => {
745
+ context.set(orderContext, [...context.get(orderContext), 'd']);
746
+ },
747
+ ];
748
+
749
+ export async function clientLoader({ context }) {
750
+ return context.get(orderContext).join(',');
751
+ }
752
+
753
+ export default function Component({ loaderData }) {
754
+ return <h2 data-route>About: {loaderData}</h2>;
755
+ }
756
+ ` ,
757
+ } ,
758
+ } ) ;
759
+
760
+ let appFixture = await createAppFixture ( fixture ) ;
761
+
762
+ let app = new PlaywrightFixture ( appFixture , page ) ;
763
+ await app . goto ( "/" ) ;
764
+ await page . waitForSelector ( '[data-route]:has-text("Index")' ) ;
765
+ expect ( await page . locator ( "[data-route]" ) . textContent ( ) ) . toBe (
766
+ "Index: a,b"
767
+ ) ;
768
+
769
+ ( await page . $ ( 'a[href="/about"]' ) ) ?. click ( ) ;
770
+ await page . waitForSelector ( '[data-route]:has-text("About")' ) ;
771
+ expect ( await page . locator ( "[data-route]" ) . textContent ( ) ) . toBe (
772
+ "About: c,d"
773
+ ) ;
774
+
775
+ appFixture . close ( ) ;
776
+ } ) ;
777
+
599
778
test ( "calls clientMiddleware before/after actions" , async ( { page } ) => {
600
779
let fixture = await createFixture ( {
601
780
files : {
@@ -1074,7 +1253,7 @@ test.describe("Middleware", () => {
1074
1253
await page . waitForSelector ( "[data-child]" ) ;
1075
1254
1076
1255
// 2 separate server requests made
1077
- expect ( requests ) . toEqual ( [
1256
+ expect ( requests . sort ( ) ) . toEqual ( [
1078
1257
expect . stringContaining ( "/parent/child.data?_routes=routes%2Fparent" ) ,
1079
1258
expect . stringContaining (
1080
1259
"/parent/child.data?_routes=routes%2Fparent.child"
@@ -1236,15 +1415,15 @@ test.describe("Middleware", () => {
1236
1415
await page . waitForSelector ( "[data-action]" ) ;
1237
1416
1238
1417
// 2 separate server requests made
1239
- expect ( requests ) . toEqual ( [
1240
- // index gets it's own due to clientLoader
1241
- expect . stringMatching (
1242
- / \/ p a r e n t \/ c h i l d \. d a t a \? _ r o u t e s = r o u t e s % 2 F p a r e n t \. c h i l d \. _ i n d e x $ /
1243
- ) ,
1418
+ expect ( requests . sort ( ) ) . toEqual ( [
1244
1419
// This is the normal request but only included parent.child because parent opted out
1245
1420
expect . stringMatching (
1246
1421
/ \/ p a r e n t \/ c h i l d \. d a t a \? _ r o u t e s = r o u t e s % 2 F p a r e n t \. c h i l d $ /
1247
1422
) ,
1423
+ // index gets it's own due to clientLoader
1424
+ expect . stringMatching (
1425
+ / \/ p a r e n t \/ c h i l d \. d a t a \? _ r o u t e s = r o u t e s % 2 F p a r e n t \. c h i l d \. _ i n d e x $ /
1426
+ ) ,
1248
1427
] ) ;
1249
1428
1250
1429
// But client middlewares only ran once for the action and once for the revalidation
0 commit comments