Skip to content

Commit 56a2641

Browse files
committed
feat(sdk): expand router path test data with patterns from React Router tests
Added comprehensive test cases discovered from React Router test suite: - Dynamic params with dashes: /courses/:foo-bar - Multiple consecutive optionals: /nested/:one?/:two?/:three?/:four? - Intercalated optionals: /nested/one?/two/three? - New mixedOptionals category: /nested/:one?/two/:three?, /one?/:two?/three/:four/* - Extended file extensions: .tar.gz, .min.js patterns - Sitemap patterns: /sitemap/:lang.xml Sources: - matchPath-test.tsx - path-matching-test.tsx - generatePath-test.tsx - special-characters-test.tsx
1 parent db50641 commit 56a2641

File tree

1 file changed

+137
-15
lines changed

1 file changed

+137
-15
lines changed

packages/sdk/src/router-path-test-data.ts

Lines changed: 137 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
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

48140
export 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
79175
export const ALL_VALID_PATHS = Object.values(VALID_ROUTER_PATHS).flat();
80176
export 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
83180
export 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
98204
export 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

Comments
 (0)