@@ -69,3 +69,150 @@ test('json data rewrite works', async ({ middlewarePages }) => {
69
69
70
70
expect ( data . pageProps . message ) . toBeDefined ( )
71
71
} )
72
+
73
+ // those tests use `fetch` instead of `page.goto` intentionally to avoid potential client rendering
74
+ // hiding any potential edge/server issues
75
+ test . describe ( 'Middleware with i18n and excluded paths' , ( ) => {
76
+ const DEFAULT_LOCALE = 'en'
77
+
78
+ /** helper function to extract JSON data from page rendering data with `<pre>{JSON.stringify(data)}</pre>` */
79
+ function extractDataFromHtml ( html : string ) : Record < string , any > {
80
+ const match = html . match ( / < p r e > (?< rawInput > [ ^ < ] + ) < \/ p r e > / )
81
+ if ( ! match || ! match . groups ?. rawInput ) {
82
+ console . error ( '<pre> not found in html input' , {
83
+ html,
84
+ } )
85
+ throw new Error ( 'Failed to extract data from HTML' )
86
+ }
87
+
88
+ const { rawInput } = match . groups
89
+ const unescapedInput = rawInput . replaceAll ( '"' , '"' )
90
+ try {
91
+ return JSON . parse ( unescapedInput )
92
+ } catch ( originalError ) {
93
+ console . error ( 'Failed to parse JSON' , {
94
+ originalError,
95
+ rawInput,
96
+ unescapedInput,
97
+ } )
98
+ }
99
+ throw new Error ( 'Failed to extract data from HTML' )
100
+ }
101
+
102
+ // those tests hit paths ending with `/json` which has special handling in middleware
103
+ // to return JSON response from middleware itself
104
+ test . describe ( 'Middleware response path' , ( ) => {
105
+ test ( 'should match on non-localized not excluded page path' , async ( {
106
+ middlewareI18nExcludedPaths,
107
+ } ) => {
108
+ const response = await fetch ( `${ middlewareI18nExcludedPaths . url } /json` )
109
+
110
+ expect ( response . headers . get ( 'x-test-used-middleware' ) ) . toBe ( 'true' )
111
+ expect ( response . status ) . toBe ( 200 )
112
+
113
+ const { nextUrlPathname, nextUrlLocale } = await response . json ( )
114
+
115
+ expect ( nextUrlPathname ) . toBe ( '/json' )
116
+ expect ( nextUrlLocale ) . toBe ( DEFAULT_LOCALE )
117
+ } )
118
+
119
+ test ( 'should match on localized not excluded page path' , async ( {
120
+ middlewareI18nExcludedPaths,
121
+ } ) => {
122
+ const response = await fetch ( `${ middlewareI18nExcludedPaths . url } /fr/json` )
123
+
124
+ expect ( response . headers . get ( 'x-test-used-middleware' ) ) . toBe ( 'true' )
125
+ expect ( response . status ) . toBe ( 200 )
126
+
127
+ const { nextUrlPathname, nextUrlLocale } = await response . json ( )
128
+
129
+ expect ( nextUrlPathname ) . toBe ( '/json' )
130
+ expect ( nextUrlLocale ) . toBe ( 'fr' )
131
+ } )
132
+ } )
133
+
134
+ // those tests hit paths that don't end with `/json` while still satisfying middleware matcher
135
+ // so middleware should pass them through to origin
136
+ test . describe ( 'Middleware passthrough' , ( ) => {
137
+ test ( 'should match on non-localized not excluded page path' , async ( {
138
+ middlewareI18nExcludedPaths,
139
+ } ) => {
140
+ const response = await fetch ( `${ middlewareI18nExcludedPaths . url } /html` )
141
+
142
+ expect ( response . headers . get ( 'x-test-used-middleware' ) ) . toBe ( 'true' )
143
+ expect ( response . status ) . toBe ( 200 )
144
+ expect ( response . headers . get ( 'content-type' ) ) . toMatch ( / t e x t \/ h t m l / )
145
+
146
+ const html = await response . text ( )
147
+ const { locale, params } = extractDataFromHtml ( html )
148
+
149
+ expect ( params ) . toMatchObject ( { catchall : [ 'html' ] } )
150
+ expect ( locale ) . toBe ( DEFAULT_LOCALE )
151
+ } )
152
+
153
+ test ( 'should match on localized not excluded page path' , async ( {
154
+ middlewareI18nExcludedPaths,
155
+ } ) => {
156
+ const response = await fetch ( `${ middlewareI18nExcludedPaths . url } /fr/html` )
157
+
158
+ expect ( response . headers . get ( 'x-test-used-middleware' ) ) . toBe ( 'true' )
159
+ expect ( response . status ) . toBe ( 200 )
160
+ expect ( response . headers . get ( 'content-type' ) ) . toMatch ( / t e x t \/ h t m l / )
161
+
162
+ const html = await response . text ( )
163
+ const { locale, params } = extractDataFromHtml ( html )
164
+
165
+ expect ( params ) . toMatchObject ( { catchall : [ 'html' ] } )
166
+ expect ( locale ) . toBe ( 'fr' )
167
+ } )
168
+ } )
169
+
170
+ // those tests hit paths that don't satisfy middleware matcher, so should go directly to origin
171
+ // without going through middleware
172
+ test . describe ( 'Middleware skipping (paths not satisfying middleware matcher)' , ( ) => {
173
+ test ( 'should NOT match on non-localized excluded API path' , async ( {
174
+ middlewareI18nExcludedPaths,
175
+ } ) => {
176
+ const response = await fetch ( `${ middlewareI18nExcludedPaths . url } /api/html` )
177
+
178
+ expect ( response . headers . get ( 'x-test-used-middleware' ) ) . not . toBe ( 'true' )
179
+ expect ( response . status ) . toBe ( 200 )
180
+
181
+ const { params } = await response . json ( )
182
+
183
+ expect ( params ) . toMatchObject ( { catchall : [ 'html' ] } )
184
+ } )
185
+
186
+ test ( 'should NOT match on non-localized excluded page path' , async ( {
187
+ middlewareI18nExcludedPaths,
188
+ } ) => {
189
+ const response = await fetch ( `${ middlewareI18nExcludedPaths . url } /excluded` )
190
+
191
+ expect ( response . headers . get ( 'x-test-used-middleware' ) ) . not . toBe ( 'true' )
192
+ expect ( response . status ) . toBe ( 200 )
193
+ expect ( response . headers . get ( 'content-type' ) ) . toMatch ( / t e x t \/ h t m l / )
194
+
195
+ const html = await response . text ( )
196
+ const { locale, params } = extractDataFromHtml ( html )
197
+
198
+ expect ( params ) . toMatchObject ( { catchall : [ 'excluded' ] } )
199
+ expect ( locale ) . toBe ( DEFAULT_LOCALE )
200
+ } )
201
+
202
+ test ( 'should NOT match on localized excluded page path' , async ( {
203
+ middlewareI18nExcludedPaths,
204
+ } ) => {
205
+ const response = await fetch ( `${ middlewareI18nExcludedPaths . url } /fr/excluded` )
206
+
207
+ expect ( response . headers . get ( 'x-test-used-middleware' ) ) . not . toBe ( 'true' )
208
+ expect ( response . status ) . toBe ( 200 )
209
+ expect ( response . headers . get ( 'content-type' ) ) . toMatch ( / t e x t \/ h t m l / )
210
+
211
+ const html = await response . text ( )
212
+ const { locale, params } = extractDataFromHtml ( html )
213
+
214
+ expect ( params ) . toMatchObject ( { catchall : [ 'excluded' ] } )
215
+ expect ( locale ) . toBe ( 'fr' )
216
+ } )
217
+ } )
218
+ } )
0 commit comments