@@ -3,7 +3,7 @@ import type {
3
3
Response as PlaywrightResponse ,
4
4
} from "@playwright/test" ;
5
5
import { test , expect } from "@playwright/test" ;
6
- import { UNSAFE_ServerMode } from "react-router" ;
6
+ import { UNSAFE_ErrorResponseImpl , UNSAFE_ServerMode } from "react-router" ;
7
7
8
8
import {
9
9
createAppFixture ,
@@ -2938,6 +2938,234 @@ test.describe("Middleware", () => {
2938
2938
appFixture . close ( ) ;
2939
2939
} ) ;
2940
2940
2941
+ test ( "bubbles response up the chain when middleware throws data() before next" , async ( {
2942
+ page,
2943
+ } ) => {
2944
+ let fixture = await createFixture (
2945
+ {
2946
+ files : {
2947
+ "react-router.config.ts" : reactRouterConfig ( {
2948
+ middleware : true ,
2949
+ } ) ,
2950
+ "vite.config.ts" : js `
2951
+ import { defineConfig } from "vite";
2952
+ import { reactRouter } from "@react-router/dev/vite";
2953
+
2954
+ export default defineConfig({
2955
+ build: { manifest: true, minify: false },
2956
+ plugins: [reactRouter()],
2957
+ });
2958
+ ` ,
2959
+ "app/routes/_index.tsx" : js `
2960
+ import { Link } from 'react-router'
2961
+ export default function Component({ loaderData }) {
2962
+ return <Link to="/a/b/c">/a/b/c</Link>;
2963
+ }
2964
+ ` ,
2965
+ "app/routes/a.tsx" : js `
2966
+ import { Outlet } from 'react-router'
2967
+ export const unstable_middleware = [
2968
+ async (_, next) => {
2969
+ let res = await next();
2970
+ res.headers.set('x-a', 'true');
2971
+ return res;
2972
+ }
2973
+ ];
2974
+ export default function Component() {
2975
+ return <Outlet/>
2976
+ }
2977
+ export function ErrorBoundary({ error }) {
2978
+ return (
2979
+ <>
2980
+ <h1 data-error>A Error Boundary</h1>
2981
+ <pre>{error.data}</pre>
2982
+ </>
2983
+ );
2984
+ }
2985
+ ` ,
2986
+ "app/routes/a.b.tsx" : js `
2987
+ import { Link, Outlet } from 'react-router'
2988
+ export const unstable_middleware = [
2989
+ async (_, next) => {
2990
+ let res = await next();
2991
+ res.headers.set('x-b', 'true');
2992
+ return res;
2993
+ }
2994
+ ];
2995
+ export default function Component({ loaderData }) {
2996
+ return <Outlet/>;
2997
+ }
2998
+ ` ,
2999
+ "app/routes/a.b.c.tsx" : js `
3000
+ import { data } from "react-router";
3001
+ export const unstable_middleware = [(_, next) => {
3002
+ throw data('C ERROR', { status: 418, statusText: "I'm a teapot" })
3003
+ }];
3004
+ // Force middleware to run on client side navs
3005
+ export function loader() {
3006
+ return null;
3007
+ }
3008
+ export default function Component({ loaderData }) {
3009
+ return <h1>C</h1>
3010
+ }
3011
+ ` ,
3012
+ } ,
3013
+ } ,
3014
+ UNSAFE_ServerMode . Development ,
3015
+ ) ;
3016
+
3017
+ let appFixture = await createAppFixture (
3018
+ fixture ,
3019
+ UNSAFE_ServerMode . Development ,
3020
+ ) ;
3021
+
3022
+ let res = await fixture . requestDocument ( "/a/b/c" ) ;
3023
+ expect ( res . status ) . toBe ( 418 ) ;
3024
+ expect ( res . headers . get ( "x-a" ) ) . toBe ( "true" ) ;
3025
+ expect ( res . headers . get ( "x-b" ) ) . toBe ( "true" ) ;
3026
+ let html = await res . text ( ) ;
3027
+ expect ( html ) . toContain ( "A Error Boundary" ) ;
3028
+ expect ( html ) . toContain ( "C ERROR" ) ;
3029
+
3030
+ let data = await fixture . requestSingleFetchData ( "/a/b/c.data" ) ;
3031
+ expect ( data . status ) . toBe ( 418 ) ;
3032
+ expect ( data . headers . get ( "x-a" ) ) . toBe ( "true" ) ;
3033
+ expect ( data . headers . get ( "x-b" ) ) . toBe ( "true" ) ;
3034
+ expect ( ( data . data as any ) [ "routes/a.b.c" ] ) . toEqual ( {
3035
+ error : new UNSAFE_ErrorResponseImpl ( 418 , "I'm a teapot" , "C ERROR" ) ,
3036
+ } ) ;
3037
+
3038
+ let app = new PlaywrightFixture ( appFixture , page ) ;
3039
+ await app . goto ( "/" ) ;
3040
+ await app . clickLink ( "/a/b/c" ) ;
3041
+ await page . waitForSelector ( "[data-error]" ) ;
3042
+ expect ( await page . locator ( "[data-error]" ) . textContent ( ) ) . toBe (
3043
+ "A Error Boundary" ,
3044
+ ) ;
3045
+ expect ( await page . locator ( "pre" ) . textContent ( ) ) . toBe ( "C ERROR" ) ;
3046
+
3047
+ appFixture . close ( ) ;
3048
+ } ) ;
3049
+
3050
+ test ( "bubbles response up the chain when middleware throws data() after next" , async ( {
3051
+ page,
3052
+ } ) => {
3053
+ let fixture = await createFixture (
3054
+ {
3055
+ files : {
3056
+ "react-router.config.ts" : reactRouterConfig ( {
3057
+ middleware : true ,
3058
+ } ) ,
3059
+ "vite.config.ts" : js `
3060
+ import { defineConfig } from "vite";
3061
+ import { reactRouter } from "@react-router/dev/vite";
3062
+
3063
+ export default defineConfig({
3064
+ build: { manifest: true, minify: false },
3065
+ plugins: [reactRouter()],
3066
+ });
3067
+ ` ,
3068
+ "app/routes/_index.tsx" : js `
3069
+ import { Link } from 'react-router'
3070
+ export default function Component({ loaderData }) {
3071
+ return <Link to="/a/b/c">/a/b/c</Link>;
3072
+ }
3073
+ ` ,
3074
+ "app/routes/a.tsx" : js `
3075
+ import { Outlet } from 'react-router'
3076
+ export const unstable_middleware = [
3077
+ async (_, next) => {
3078
+ let res = await next();
3079
+ res.headers.set('x-a', 'true');
3080
+ return res;
3081
+ }
3082
+ ];
3083
+ export function loader() {
3084
+ return "A LOADER";
3085
+ }
3086
+ export default function Component() {
3087
+ return <Outlet/>
3088
+ }
3089
+ export function ErrorBoundary({ error, loaderData }) {
3090
+ return (
3091
+ <>
3092
+ <h1 data-error>A Error Boundary</h1>
3093
+ <pre>{error.data}</pre>
3094
+ <p>{loaderData}</p>
3095
+ </>
3096
+ );
3097
+ }
3098
+ ` ,
3099
+ "app/routes/a.b.tsx" : js `
3100
+ import { Link, Outlet } from 'react-router'
3101
+ export const unstable_middleware = [
3102
+ async (_, next) => {
3103
+ let res = await next();
3104
+ res.headers.set('x-b', 'true');
3105
+ return res;
3106
+ }
3107
+ ];
3108
+ export default function Component({ loaderData }) {
3109
+ return <Outlet/>;
3110
+ }
3111
+ ` ,
3112
+ "app/routes/a.b.c.tsx" : js `
3113
+ import { data } from "react-router";
3114
+ export const unstable_middleware = [async (_, next) => {
3115
+ let res = await next();
3116
+ throw data('C ERROR', { status: 418, statusText: "I'm a teapot" })
3117
+ }];
3118
+ // Force middleware to run on client side navs
3119
+ export function loader() {
3120
+ return null;
3121
+ }
3122
+ export default function Component({ loaderData }) {
3123
+ return <h1>C</h1>
3124
+ }
3125
+ ` ,
3126
+ } ,
3127
+ } ,
3128
+ UNSAFE_ServerMode . Development ,
3129
+ ) ;
3130
+
3131
+ let appFixture = await createAppFixture (
3132
+ fixture ,
3133
+ UNSAFE_ServerMode . Development ,
3134
+ ) ;
3135
+
3136
+ let res = await fixture . requestDocument ( "/a/b/c" ) ;
3137
+ expect ( res . status ) . toBe ( 418 ) ;
3138
+ expect ( res . headers . get ( "x-a" ) ) . toBe ( "true" ) ;
3139
+ expect ( res . headers . get ( "x-b" ) ) . toBe ( "true" ) ;
3140
+ let html = await res . text ( ) ;
3141
+ expect ( html ) . toContain ( "A Error Boundary" ) ;
3142
+ expect ( html ) . toContain ( "C ERROR" ) ;
3143
+ expect ( html ) . toContain ( "A LOADER" ) ;
3144
+
3145
+ let data = await fixture . requestSingleFetchData ( "/a/b/c.data" ) ;
3146
+ expect ( data . status ) . toBe ( 418 ) ;
3147
+ expect ( data . headers . get ( "x-a" ) ) . toBe ( "true" ) ;
3148
+ expect ( data . headers . get ( "x-b" ) ) . toBe ( "true" ) ;
3149
+ expect ( ( data . data as any ) [ "routes/a" ] ) . toEqual ( {
3150
+ data : "A LOADER" ,
3151
+ } ) ;
3152
+ expect ( ( data . data as any ) [ "routes/a.b.c" ] ) . toEqual ( {
3153
+ error : new UNSAFE_ErrorResponseImpl ( 418 , "I'm a teapot" , "C ERROR" ) ,
3154
+ } ) ;
3155
+
3156
+ let app = new PlaywrightFixture ( appFixture , page ) ;
3157
+ await app . goto ( "/" ) ;
3158
+ await app . clickLink ( "/a/b/c" ) ;
3159
+ await page . waitForSelector ( "[data-error]" ) ;
3160
+ expect ( await page . locator ( "[data-error]" ) . textContent ( ) ) . toBe (
3161
+ "A Error Boundary" ,
3162
+ ) ;
3163
+ expect ( await page . locator ( "pre" ) . textContent ( ) ) . toBe ( "C ERROR" ) ;
3164
+ expect ( await page . locator ( "p" ) . textContent ( ) ) . toBe ( "A LOADER" ) ;
3165
+
3166
+ appFixture . close ( ) ;
3167
+ } ) ;
3168
+
2941
3169
test ( "still calls middleware for all matches on granular data requests" , async ( {
2942
3170
page,
2943
3171
} ) => {
0 commit comments