88 *
99 * These paths are used for pages, redirects, and any routable URL.
1010 * Add new test cases here to ensure all layers are tested with the same data.
11+ *
12+ * Reference: React Router path matching tests
13+ * https://github.com/remix-run/react-router/tree/main/packages/react-router/__tests__
1114 */
1215
1316// These test cases define what paths should be valid/invalid across ALL layers
@@ -16,20 +19,90 @@ export const VALID_ROUTER_PATHS = {
1619 // Basic paths
1720 basic : [ "/about" , "/blog" , "/contact-us" , "/products/item-1" ] ,
1821
19- // Patterns (wildcards, params)
20- patterns : [
21- "/blog/*" ,
22- "/blog/:slug" ,
23- "/blog/:slug?" ,
24- "/posts/:year/:month/*" ,
25- "/:category/:id" ,
22+ // Deep nesting
23+ deepNesting : [
24+ "/a/b/c/d/e" ,
25+ "/users/123/posts/456/comments/789" ,
26+ "/api/v1/resources/items" ,
27+ ] ,
28+
29+ // Dynamic segments (React Router: /:param)
30+ dynamicSegments : [
31+ "/:id" ,
32+ "/users/:id" ,
33+ "/users/:userId/posts/:postId" ,
34+ "/blog/:year/:month/:day/:slug" ,
35+ "/courses/:foo-bar" , // params can contain dashes
36+ ] ,
37+
38+ // Optional dynamic segments (React Router: /:param?)
39+ optionalDynamic : [
40+ "/:lang?/about" ,
41+ "/user/:id/:tab?" ,
42+ "/:lang?/user/:id?" ,
43+ "/docs/:version?/:page?" ,
44+ "/nested/:one?/:two?/:three?/:four?" , // up to 4 consecutive optionals
45+ "/:one?/:two?/:three?" , // all optional at root
46+ ] ,
47+
48+ // Optional static segments (React Router: /segment?)
49+ optionalStatic : [
50+ "/en?/about" ,
51+ "/api/v1?/users" ,
52+ "/school?/user/:id" ,
53+ "/admin?/dashboard" ,
54+ "/nested/one?/two?" , // consecutive static optionals
55+ "/nested/one?/two/three?" , // intercalated static optionals
56+ ] ,
57+
58+ // Mixed optional patterns (intercalated static and dynamic)
59+ mixedOptionals : [
60+ "/nested/:one?/two/:three?" , // optional, required, optional
61+ "/one?/:two?/three/:four/*" , // mixed optionals with splat
62+ "/one/:two?/three/:four?/:five?" , // complex mixed pattern
2663 ] ,
2764
65+ // Wildcard/splat routes (React Router: /*)
66+ wildcards : [ "/*" , "/blog/*" , "/docs/*" , "/files/*" , "/users/:id/files/*" ] ,
67+
2868 // Query strings and fragments
29- queryAndFragments : [ "/search?q=test" , "/page#section" , "/path?a=1&b=2#top" ] ,
69+ queryAndFragments : [
70+ "/search?q=test" ,
71+ "/page#section" ,
72+ "/path?a=1&b=2#top" ,
73+ "/products?category=shoes&sort=price" ,
74+ ] ,
3075
3176 // URL-encoded characters
32- urlEncoded : [ "/hello%20world" , "/%E6%B8%AF%E8%81%9E" , "/path%2Fwith%2Fslash" ] ,
77+ urlEncoded : [
78+ "/hello%20world" ,
79+ "/%E6%B8%AF%E8%81%9E" ,
80+ "/path%2Fwith%2Fslash" ,
81+ "/users%3Fid%3D123" , // ?id=123 encoded
82+ ] ,
83+
84+ // Special characters allowed in paths (from React Router special-characters-test.tsx)
85+ // Note: Some chars have special meaning in URLPattern regex and must be URL-encoded
86+ specialChars : [
87+ "/path-with-dash" ,
88+ "/path_with_underscore" ,
89+ "/path.with.dots" ,
90+ "/path~tilde" ,
91+ "/path!exclaim" ,
92+ "/path@at" ,
93+ "/path$dollar" ,
94+ "/path'apostrophe" ,
95+ "/path,comma" ,
96+ "/path;semicolon" ,
97+ "/path=equals" ,
98+ ] ,
99+
100+ // Characters that are valid in URLs but have special meaning in URLPattern
101+ // These need to be URL-encoded when used literally (not as pattern syntax)
102+ specialCharsNeedEncoding : [
103+ "/path%28parens%29" , // parentheses encoded
104+ "/path%2Bplus" , // plus encoded
105+ ] ,
33106
34107 // Non-Latin characters (Unicode/UTF-8)
35108 chinese : [ "/关于我们" , "/产品/手机" , "/港聞" , "/繁體中文" ] ,
@@ -38,21 +111,40 @@ export const VALID_ROUTER_PATHS = {
38111 cyrillic : [ "/привет" , "/о-нас" , "/блог/статья" ] ,
39112 arabic : [ "/مرحبا" , "/عن-الشركة" ] ,
40113 hebrew : [ "/שלום" , "/אודות" ] ,
114+ thai : [ "/สวัสดี" , "/ภาษาไทย" ] ,
41115 greek : [ "/γεια" , "/σχετικά" ] ,
42- european : [ "/über-uns" , "/café" , "/niño" , "/résumé" ] ,
116+ european : [ "/über-uns" , "/café" , "/niño" , "/résumé" , "/naïve" ] ,
43117
44118 // Mixed Latin and non-Latin
45- mixed : [ "/blog/关于" , "/news/港聞" , "/category/日本語" ] ,
119+ mixed : [ "/blog/关于" , "/news/港聞" , "/category/日本語" , "/user/bücherwurm" ] ,
120+
121+ // File extensions in paths (from React Router generatePath-test.tsx)
122+ fileExtensions : [
123+ "/books/:id.json" ,
124+ "/api/:resource.xml" ,
125+ "/images/:name.png" ,
126+ "/docs/:page.html" ,
127+ "/sitemap/:lang.xml" , // param before extension
128+ "/:lang.html" , // root level param with extension
129+ "/files/:name.tar.gz" , // multiple extensions
130+ "/:file.min.js" , // minified JS pattern
131+ ] ,
132+
133+ // Base64-like segments (from React Router matchRoutes-test.tsx)
134+ base64Segments : [ "/users/VXNlcnM6MQ==" , "/items/YWJjZGVm" ] ,
135+
136+ // Emoji paths (modern Unicode)
137+ emoji : [ "/🏠" , "/blog/🎉" , "/products/👟" ] ,
46138} as const ;
47139
48140export const INVALID_ROUTER_PATHS = {
49- // Empty or root only
141+ // Empty or root only (for redirects, root is not a valid source)
50142 empty : [ "" , "/" ] ,
51143
52144 // Spaces (must be URL-encoded)
53145 spaces : [ "/hello world" , "/path with spaces" ] ,
54146
55- // URL-unsafe characters
147+ // URL-unsafe characters (RFC 3986)
56148 unsafe : [
57149 "/path<script>" ,
58150 "/path>other" ,
@@ -61,9 +153,10 @@ export const INVALID_ROUTER_PATHS = {
61153 "/path|other" ,
62154 "/path\\other" ,
63155 "/path[0]" ,
156+ "/path`backtick" ,
64157 ] ,
65158
66- // Reserved paths
159+ // Reserved paths (Webstudio-specific)
67160 reserved : [ "/s" , "/s/css" , "/s/uploads" , "/build" , "/build/main.js" ] ,
68161
69162 // Invalid structure
@@ -73,37 +166,66 @@ export const INVALID_ROUTER_PATHS = {
73166 "/double//slash" ,
74167 "//leading-double" ,
75168 ] ,
169+
170+ // Control characters
171+ controlChars : [ "/path\x00null" , "/path\x1fnewline" ] ,
76172} as const ;
77173
78174// Flattened arrays for convenience
79175export const ALL_VALID_PATHS = Object . values ( VALID_ROUTER_PATHS ) . flat ( ) ;
80176export const ALL_INVALID_PATHS = Object . values ( INVALID_ROUTER_PATHS ) . flat ( ) ;
81177
82178// Paths that are specifically for testing URLPattern matching (no query/fragment)
179+ // These are paths that work with URLPattern API
83180export const VALID_URLPATTERN_PATHS = [
84181 ...VALID_ROUTER_PATHS . basic ,
85- ...VALID_ROUTER_PATHS . patterns ,
182+ ...VALID_ROUTER_PATHS . deepNesting ,
183+ ...VALID_ROUTER_PATHS . dynamicSegments ,
184+ ...VALID_ROUTER_PATHS . optionalDynamic ,
185+ // Note: optionalStatic uses React Router syntax which differs from URLPattern
186+ ...VALID_ROUTER_PATHS . wildcards ,
187+ ...VALID_ROUTER_PATHS . specialChars ,
188+ ...VALID_ROUTER_PATHS . specialCharsNeedEncoding ,
86189 ...VALID_ROUTER_PATHS . chinese ,
87190 ...VALID_ROUTER_PATHS . japanese ,
88191 ...VALID_ROUTER_PATHS . korean ,
89192 ...VALID_ROUTER_PATHS . cyrillic ,
90193 ...VALID_ROUTER_PATHS . arabic ,
91194 ...VALID_ROUTER_PATHS . hebrew ,
195+ ...VALID_ROUTER_PATHS . thai ,
92196 ...VALID_ROUTER_PATHS . greek ,
93197 ...VALID_ROUTER_PATHS . european ,
94198 ...VALID_ROUTER_PATHS . mixed ,
199+ ...VALID_ROUTER_PATHS . base64Segments ,
200+ ...VALID_ROUTER_PATHS . emoji ,
95201] as const ;
96202
97203// Static paths (no wildcards/params) for testing route generation
98204export const STATIC_PATHS = [
99205 ...VALID_ROUTER_PATHS . basic ,
206+ ...VALID_ROUTER_PATHS . deepNesting ,
207+ ...VALID_ROUTER_PATHS . specialChars ,
208+ ...VALID_ROUTER_PATHS . specialCharsNeedEncoding ,
100209 ...VALID_ROUTER_PATHS . chinese ,
101210 ...VALID_ROUTER_PATHS . japanese ,
102211 ...VALID_ROUTER_PATHS . korean ,
103212 ...VALID_ROUTER_PATHS . cyrillic ,
104213 ...VALID_ROUTER_PATHS . arabic ,
105214 ...VALID_ROUTER_PATHS . hebrew ,
215+ ...VALID_ROUTER_PATHS . thai ,
106216 ...VALID_ROUTER_PATHS . greek ,
107217 ...VALID_ROUTER_PATHS . european ,
108218 ...VALID_ROUTER_PATHS . mixed ,
219+ ...VALID_ROUTER_PATHS . base64Segments ,
220+ ...VALID_ROUTER_PATHS . emoji ,
221+ ] as const ;
222+
223+ // Pattern paths (with dynamic segments, wildcards, or optional segments)
224+ export const PATTERN_PATHS = [
225+ ...VALID_ROUTER_PATHS . dynamicSegments ,
226+ ...VALID_ROUTER_PATHS . optionalDynamic ,
227+ ...VALID_ROUTER_PATHS . optionalStatic ,
228+ ...VALID_ROUTER_PATHS . mixedOptionals ,
229+ ...VALID_ROUTER_PATHS . wildcards ,
230+ ...VALID_ROUTER_PATHS . fileExtensions ,
109231] as const ;
0 commit comments