one 1.1.473 → 1.1.475

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/dist/cjs/Root.cjs +5 -1
  2. package/dist/cjs/Root.js +2 -2
  3. package/dist/cjs/Root.js.map +1 -1
  4. package/dist/cjs/Root.native.js +5 -2
  5. package/dist/cjs/Root.native.js.map +2 -2
  6. package/dist/cjs/createApp.cjs +2 -0
  7. package/dist/cjs/createApp.js +16 -2
  8. package/dist/cjs/createApp.js.map +1 -1
  9. package/dist/cjs/createApp.native.js +1 -0
  10. package/dist/cjs/createApp.native.js.map +2 -2
  11. package/dist/cjs/fork/__tests__/getPathFromState.test.cjs +1440 -0
  12. package/dist/cjs/fork/__tests__/getPathFromState.test.js +1559 -0
  13. package/dist/cjs/fork/__tests__/getPathFromState.test.js.map +6 -0
  14. package/dist/cjs/fork/__tests__/getPathFromState.test.native.js +1726 -0
  15. package/dist/cjs/fork/__tests__/getPathFromState.test.native.js.map +6 -0
  16. package/dist/cjs/fork/__tests__/getStateFromPath.test.cjs +2565 -0
  17. package/dist/cjs/fork/__tests__/getStateFromPath.test.js +2702 -0
  18. package/dist/cjs/fork/__tests__/getStateFromPath.test.js.map +6 -0
  19. package/dist/cjs/fork/__tests__/getStateFromPath.test.native.js +2861 -0
  20. package/dist/cjs/fork/__tests__/getStateFromPath.test.native.js.map +6 -0
  21. package/dist/cjs/fork/getPathFromState.cjs +2 -1
  22. package/dist/cjs/fork/getPathFromState.js +1 -1
  23. package/dist/cjs/fork/getPathFromState.js.map +1 -1
  24. package/dist/cjs/fork/getPathFromState.native.js +10 -5
  25. package/dist/cjs/fork/getPathFromState.native.js.map +1 -1
  26. package/dist/cjs/fork/getPathFromState.test.cjs +113 -0
  27. package/dist/cjs/fork/getPathFromState.test.js +122 -0
  28. package/dist/cjs/fork/getPathFromState.test.js.map +6 -0
  29. package/dist/cjs/fork/getPathFromState.test.native.js +135 -0
  30. package/dist/cjs/fork/getPathFromState.test.native.js.map +6 -0
  31. package/dist/cjs/fork/getStateFromPath.test.cjs +229 -0
  32. package/dist/cjs/fork/getStateFromPath.test.js +290 -0
  33. package/dist/cjs/fork/getStateFromPath.test.js.map +6 -0
  34. package/dist/cjs/fork/getStateFromPath.test.native.js +374 -0
  35. package/dist/cjs/fork/getStateFromPath.test.native.js.map +6 -0
  36. package/dist/cjs/render.cjs +1 -1
  37. package/dist/cjs/render.js +1 -1
  38. package/dist/cjs/render.js.map +1 -1
  39. package/dist/cjs/router/FlagsContext.cjs +27 -0
  40. package/dist/cjs/router/FlagsContext.js +22 -0
  41. package/dist/cjs/router/FlagsContext.js.map +6 -0
  42. package/dist/cjs/router/FlagsContext.native.js +26 -0
  43. package/dist/cjs/router/FlagsContext.native.js.map +6 -0
  44. package/dist/cjs/router/getRoutes.cjs +11 -1
  45. package/dist/cjs/router/getRoutes.js +11 -1
  46. package/dist/cjs/router/getRoutes.js.map +1 -1
  47. package/dist/cjs/router/getRoutes.native.js +11 -1
  48. package/dist/cjs/router/getRoutes.native.js.map +2 -2
  49. package/dist/cjs/router/matchers.test.cjs +38 -0
  50. package/dist/cjs/router/matchers.test.js +42 -0
  51. package/dist/cjs/router/matchers.test.js.map +6 -0
  52. package/dist/cjs/router/matchers.test.native.js +39 -0
  53. package/dist/cjs/router/matchers.test.native.js.map +6 -0
  54. package/dist/cjs/router/router.cjs +3 -35
  55. package/dist/cjs/router/router.js +2 -26
  56. package/dist/cjs/router/router.js.map +1 -1
  57. package/dist/cjs/router/router.native.js +2 -33
  58. package/dist/cjs/router/router.native.js.map +2 -2
  59. package/dist/cjs/router/utils/getNavigateAction.cjs +61 -0
  60. package/dist/cjs/router/utils/getNavigateAction.js +46 -0
  61. package/dist/cjs/router/utils/getNavigateAction.js.map +6 -0
  62. package/dist/cjs/router/utils/getNavigateAction.native.js +58 -0
  63. package/dist/cjs/router/utils/getNavigateAction.native.js.map +6 -0
  64. package/dist/cjs/router/utils/getNavigateAction.test.cjs +259 -0
  65. package/dist/cjs/router/utils/getNavigateAction.test.js +295 -0
  66. package/dist/cjs/router/utils/getNavigateAction.test.js.map +6 -0
  67. package/dist/cjs/router/utils/getNavigateAction.test.native.js +330 -0
  68. package/dist/cjs/router/utils/getNavigateAction.test.native.js.map +6 -0
  69. package/dist/cjs/testing-utils.cjs +63 -0
  70. package/dist/cjs/testing-utils.js +55 -0
  71. package/dist/cjs/testing-utils.js.map +6 -0
  72. package/dist/cjs/testing-utils.native.js +79 -0
  73. package/dist/cjs/testing-utils.native.js.map +6 -0
  74. package/dist/cjs/views/Navigator.cjs +8 -1
  75. package/dist/cjs/views/Navigator.js +25 -11
  76. package/dist/cjs/views/Navigator.js.map +1 -1
  77. package/dist/cjs/views/Navigator.native.js +7 -4
  78. package/dist/cjs/views/Navigator.native.js.map +2 -2
  79. package/dist/cjs/vite/one.cjs +6 -1
  80. package/dist/cjs/vite/one.js +6 -1
  81. package/dist/cjs/vite/one.js.map +1 -1
  82. package/dist/cjs/vite/one.native.js +7 -2
  83. package/dist/cjs/vite/one.native.js.map +2 -2
  84. package/dist/cjs/vite/plugins/virtualEntryPlugin.cjs +2 -0
  85. package/dist/cjs/vite/plugins/virtualEntryPlugin.js +2 -0
  86. package/dist/cjs/vite/plugins/virtualEntryPlugin.js.map +1 -1
  87. package/dist/cjs/vite/plugins/virtualEntryPlugin.native.js +2 -0
  88. package/dist/cjs/vite/plugins/virtualEntryPlugin.native.js.map +2 -2
  89. package/dist/esm/Root.js +2 -1
  90. package/dist/esm/Root.js.map +1 -1
  91. package/dist/esm/Root.mjs +5 -1
  92. package/dist/esm/Root.mjs.map +1 -1
  93. package/dist/esm/Root.native.js +5 -1
  94. package/dist/esm/Root.native.js.map +1 -1
  95. package/dist/esm/createApp.js +16 -2
  96. package/dist/esm/createApp.js.map +1 -1
  97. package/dist/esm/createApp.mjs +2 -0
  98. package/dist/esm/createApp.mjs.map +1 -1
  99. package/dist/esm/createApp.native.js +1 -0
  100. package/dist/esm/createApp.native.js.map +1 -1
  101. package/dist/esm/fork/__tests__/getPathFromState.test.js +1561 -0
  102. package/dist/esm/fork/__tests__/getPathFromState.test.js.map +6 -0
  103. package/dist/esm/fork/__tests__/getPathFromState.test.mjs +1441 -0
  104. package/dist/esm/fork/__tests__/getPathFromState.test.mjs.map +1 -0
  105. package/dist/esm/fork/__tests__/getPathFromState.test.native.js +1580 -0
  106. package/dist/esm/fork/__tests__/getPathFromState.test.native.js.map +1 -0
  107. package/dist/esm/fork/__tests__/getStateFromPath.test.js +2706 -0
  108. package/dist/esm/fork/__tests__/getStateFromPath.test.js.map +6 -0
  109. package/dist/esm/fork/__tests__/getStateFromPath.test.mjs +2566 -0
  110. package/dist/esm/fork/__tests__/getStateFromPath.test.mjs.map +1 -0
  111. package/dist/esm/fork/__tests__/getStateFromPath.test.native.js +2636 -0
  112. package/dist/esm/fork/__tests__/getStateFromPath.test.native.js.map +1 -0
  113. package/dist/esm/fork/getPathFromState.js +1 -1
  114. package/dist/esm/fork/getPathFromState.js.map +1 -1
  115. package/dist/esm/fork/getPathFromState.mjs +2 -1
  116. package/dist/esm/fork/getPathFromState.mjs.map +1 -1
  117. package/dist/esm/fork/getPathFromState.native.js +9 -5
  118. package/dist/esm/fork/getPathFromState.native.js.map +1 -1
  119. package/dist/esm/fork/getPathFromState.test.js +123 -0
  120. package/dist/esm/fork/getPathFromState.test.js.map +6 -0
  121. package/dist/esm/fork/getPathFromState.test.mjs +114 -0
  122. package/dist/esm/fork/getPathFromState.test.mjs.map +1 -0
  123. package/dist/esm/fork/getPathFromState.test.native.js +122 -0
  124. package/dist/esm/fork/getPathFromState.test.native.js.map +1 -0
  125. package/dist/esm/fork/getStateFromPath.test.js +294 -0
  126. package/dist/esm/fork/getStateFromPath.test.js.map +6 -0
  127. package/dist/esm/fork/getStateFromPath.test.mjs +230 -0
  128. package/dist/esm/fork/getStateFromPath.test.mjs.map +1 -0
  129. package/dist/esm/fork/getStateFromPath.test.native.js +233 -0
  130. package/dist/esm/fork/getStateFromPath.test.native.js.map +1 -0
  131. package/dist/esm/render.js +1 -1
  132. package/dist/esm/render.js.map +1 -1
  133. package/dist/esm/render.mjs +1 -1
  134. package/dist/esm/render.mjs.map +1 -1
  135. package/dist/esm/router/FlagsContext.js +6 -0
  136. package/dist/esm/router/FlagsContext.js.map +6 -0
  137. package/dist/esm/router/FlagsContext.mjs +4 -0
  138. package/dist/esm/router/FlagsContext.mjs.map +1 -0
  139. package/dist/esm/router/FlagsContext.native.js +4 -0
  140. package/dist/esm/router/FlagsContext.native.js.map +1 -0
  141. package/dist/esm/router/getRoutes.js +11 -1
  142. package/dist/esm/router/getRoutes.js.map +1 -1
  143. package/dist/esm/router/getRoutes.mjs +11 -1
  144. package/dist/esm/router/getRoutes.mjs.map +1 -1
  145. package/dist/esm/router/getRoutes.native.js +11 -1
  146. package/dist/esm/router/getRoutes.native.js.map +1 -1
  147. package/dist/esm/router/matchers.test.js +50 -0
  148. package/dist/esm/router/matchers.test.js.map +6 -0
  149. package/dist/esm/router/matchers.test.mjs +39 -0
  150. package/dist/esm/router/matchers.test.mjs.map +1 -0
  151. package/dist/esm/router/matchers.test.native.js +39 -0
  152. package/dist/esm/router/matchers.test.native.js.map +1 -0
  153. package/dist/esm/router/router.js +1 -26
  154. package/dist/esm/router/router.js.map +1 -1
  155. package/dist/esm/router/router.mjs +1 -33
  156. package/dist/esm/router/router.mjs.map +1 -1
  157. package/dist/esm/router/router.native.js +1 -37
  158. package/dist/esm/router/router.native.js.map +1 -1
  159. package/dist/esm/router/utils/getNavigateAction.js +32 -0
  160. package/dist/esm/router/utils/getNavigateAction.js.map +6 -0
  161. package/dist/esm/router/utils/getNavigateAction.mjs +38 -0
  162. package/dist/esm/router/utils/getNavigateAction.mjs.map +1 -0
  163. package/dist/esm/router/utils/getNavigateAction.native.js +42 -0
  164. package/dist/esm/router/utils/getNavigateAction.native.js.map +1 -0
  165. package/dist/esm/router/utils/getNavigateAction.test.js +296 -0
  166. package/dist/esm/router/utils/getNavigateAction.test.js.map +6 -0
  167. package/dist/esm/router/utils/getNavigateAction.test.mjs +260 -0
  168. package/dist/esm/router/utils/getNavigateAction.test.mjs.map +1 -0
  169. package/dist/esm/router/utils/getNavigateAction.test.native.js +273 -0
  170. package/dist/esm/router/utils/getNavigateAction.test.native.js.map +1 -0
  171. package/dist/esm/testing-utils.js +33 -0
  172. package/dist/esm/testing-utils.js.map +6 -0
  173. package/dist/esm/testing-utils.mjs +27 -0
  174. package/dist/esm/testing-utils.mjs.map +1 -0
  175. package/dist/esm/testing-utils.native.js +38 -0
  176. package/dist/esm/testing-utils.native.js.map +1 -0
  177. package/dist/esm/views/Navigator.js +29 -11
  178. package/dist/esm/views/Navigator.js.map +1 -1
  179. package/dist/esm/views/Navigator.mjs +8 -1
  180. package/dist/esm/views/Navigator.mjs.map +1 -1
  181. package/dist/esm/views/Navigator.native.js +8 -2
  182. package/dist/esm/views/Navigator.native.js.map +1 -1
  183. package/dist/esm/vite/one.js +6 -1
  184. package/dist/esm/vite/one.js.map +1 -1
  185. package/dist/esm/vite/one.mjs +6 -1
  186. package/dist/esm/vite/one.mjs.map +1 -1
  187. package/dist/esm/vite/one.native.js +9 -2
  188. package/dist/esm/vite/one.native.js.map +1 -1
  189. package/dist/esm/vite/plugins/virtualEntryPlugin.js +2 -0
  190. package/dist/esm/vite/plugins/virtualEntryPlugin.js.map +1 -1
  191. package/dist/esm/vite/plugins/virtualEntryPlugin.mjs +2 -0
  192. package/dist/esm/vite/plugins/virtualEntryPlugin.mjs.map +1 -1
  193. package/dist/esm/vite/plugins/virtualEntryPlugin.native.js +2 -0
  194. package/dist/esm/vite/plugins/virtualEntryPlugin.native.js.map +1 -1
  195. package/package.json +13 -10
  196. package/src/Root.tsx +12 -2
  197. package/src/createApp.native.tsx +8 -2
  198. package/src/createApp.tsx +18 -3
  199. package/src/fork/__tests__/README.md +8 -0
  200. package/src/fork/__tests__/getPathFromState.test.tsx +1809 -0
  201. package/src/fork/__tests__/getStateFromPath.test.tsx +3188 -0
  202. package/src/fork/getPathFromState.test.ts +146 -0
  203. package/src/fork/getPathFromState.ts +1 -1
  204. package/src/fork/getStateFromPath.test.ts +345 -0
  205. package/src/render.tsx +3 -3
  206. package/src/router/FlagsContext.ts +4 -0
  207. package/src/router/getRoutes.ts +14 -2
  208. package/src/router/matchers.test.ts +120 -0
  209. package/src/router/router.ts +1 -113
  210. package/src/router/utils/getNavigateAction.test.ts +334 -0
  211. package/src/router/utils/getNavigateAction.ts +120 -0
  212. package/src/testing-utils.ts +56 -0
  213. package/src/views/Navigator.tsx +34 -10
  214. package/src/vite/one.ts +5 -0
  215. package/src/vite/plugins/virtualEntryPlugin.ts +4 -1
  216. package/src/vite/types.ts +18 -0
  217. package/types/Root.d.ts +1 -0
  218. package/types/Root.d.ts.map +1 -1
  219. package/types/createApp.d.ts +2 -0
  220. package/types/createApp.d.ts.map +1 -1
  221. package/types/createApp.native.d.ts +2 -0
  222. package/types/createApp.native.d.ts.map +1 -1
  223. package/types/fork/getPathFromState.test.d.ts +2 -0
  224. package/types/fork/getPathFromState.test.d.ts.map +1 -0
  225. package/types/fork/getStateFromPath.test.d.ts +2 -0
  226. package/types/fork/getStateFromPath.test.d.ts.map +1 -0
  227. package/types/router/FlagsContext.d.ts +3 -0
  228. package/types/router/FlagsContext.d.ts.map +1 -0
  229. package/types/router/getRoutes.d.ts.map +1 -1
  230. package/types/router/matchers.test.d.ts +2 -0
  231. package/types/router/matchers.test.d.ts.map +1 -0
  232. package/types/router/router.d.ts.map +1 -1
  233. package/types/router/utils/getNavigateAction.d.ts +17 -0
  234. package/types/router/utils/getNavigateAction.d.ts.map +1 -0
  235. package/types/router/utils/getNavigateAction.test.d.ts +2 -0
  236. package/types/router/utils/getNavigateAction.test.d.ts.map +1 -0
  237. package/types/testing-utils.d.ts +26 -0
  238. package/types/testing-utils.d.ts.map +1 -0
  239. package/types/views/Navigator.d.ts +1 -1
  240. package/types/views/Navigator.d.ts.map +1 -1
  241. package/types/vite/one.d.ts.map +1 -1
  242. package/types/vite/plugins/virtualEntryPlugin.d.ts +2 -0
  243. package/types/vite/plugins/virtualEntryPlugin.d.ts.map +1 -1
  244. package/types/vite/types.d.ts +16 -0
  245. package/types/vite/types.d.ts.map +1 -1
@@ -0,0 +1,1809 @@
1
+ /**
2
+ * This file is copied from the react-navigation repo.
3
+ *
4
+ * Please refrain from making changes to this file, as it will make merging updates from the upstream harder.
5
+ * All modifications except formatting should be marked with `// @modified` comment.
6
+ */
7
+
8
+ // @modified - start: use vitest instead of jest
9
+ // import { expect, test } from '@jest/globals';
10
+ import { expect, test } from 'vitest'
11
+ // @modified - end: use vitest instead of jest
12
+
13
+ import type { NavigationState, PartialState } from '@react-navigation/routers'
14
+
15
+ import { getPathFromState } from '../getPathFromState'
16
+ import { getStateFromPath } from '../getStateFromPath'
17
+
18
+ type State = PartialState<NavigationState>
19
+
20
+ test('converts state to path string', () => {
21
+ const state = {
22
+ routes: [
23
+ {
24
+ name: 'foo',
25
+ state: {
26
+ index: 1,
27
+ routes: [
28
+ { name: 'boo' },
29
+ {
30
+ name: 'bar',
31
+ params: { fruit: 'apple' },
32
+ state: {
33
+ routes: [
34
+ {
35
+ name: 'baz qux',
36
+ params: { author: 'jane', valid: true },
37
+ },
38
+ ],
39
+ },
40
+ },
41
+ ],
42
+ },
43
+ },
44
+ ],
45
+ }
46
+
47
+ const path = '/foo/bar/baz%20qux?author=jane&valid=true'
48
+
49
+ expect(getPathFromState<object>(state)).toBe(path)
50
+ expect(getPathFromState<object>(getStateFromPath<object>(path) as State)).toBe(path)
51
+ })
52
+
53
+ test('converts state to path string with config', () => {
54
+ const path = '/few/bar/sweet/apple/baz/jane?id=x10&valid=true'
55
+ const config = {
56
+ screens: {
57
+ Foo: {
58
+ path: 'few',
59
+ screens: {
60
+ Bar: {
61
+ path: 'bar/:type/:fruit',
62
+ screens: {
63
+ Baz: {
64
+ path: 'baz/:author',
65
+ parse: {
66
+ author: (author: string) =>
67
+ author.replace(/^\w/, (c) => c.toUpperCase()),
68
+ id: (id: string) => Number(id.replace(/^x/, '')),
69
+ valid: Boolean,
70
+ },
71
+ stringify: {
72
+ author: (author: string) => author.toLowerCase(),
73
+ id: (id: number) => `x${id}`,
74
+ },
75
+ },
76
+ },
77
+ },
78
+ },
79
+ },
80
+ },
81
+ }
82
+
83
+ const state = {
84
+ routes: [
85
+ {
86
+ name: 'Foo',
87
+ state: {
88
+ index: 1,
89
+ routes: [
90
+ { name: 'boo' },
91
+ {
92
+ name: 'Bar',
93
+ params: { fruit: 'apple', type: 'sweet', avaliable: false },
94
+ state: {
95
+ routes: [
96
+ {
97
+ name: 'Baz',
98
+ params: {
99
+ author: 'Jane',
100
+ id: 10,
101
+ valid: true,
102
+ },
103
+ },
104
+ ],
105
+ },
106
+ },
107
+ ],
108
+ },
109
+ },
110
+ ],
111
+ }
112
+
113
+ expect(getPathFromState<object>(state, config)).toBe(path)
114
+ expect(
115
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
116
+ ).toBe(path)
117
+ })
118
+
119
+ test('prepends trailing slash to path', () => {
120
+ expect(
121
+ getPathFromState<object>({
122
+ routes: [
123
+ {
124
+ name: 'foo',
125
+ state: {
126
+ routes: [{ name: 'bar' }],
127
+ },
128
+ },
129
+ ],
130
+ })
131
+ ).toBe('/foo/bar')
132
+
133
+ expect(
134
+ getPathFromState<object>({
135
+ routes: [
136
+ {
137
+ name: 'foo',
138
+ state: {
139
+ routes: [{ name: 'bar', path: 'foo/bar' }],
140
+ },
141
+ },
142
+ ],
143
+ })
144
+ ).toBe('/foo/bar')
145
+ })
146
+
147
+ test('handles route without param', () => {
148
+ const path = '/foo/bar'
149
+ const state = {
150
+ routes: [
151
+ {
152
+ name: 'foo',
153
+ state: {
154
+ routes: [{ name: 'bar' }],
155
+ },
156
+ },
157
+ ],
158
+ }
159
+
160
+ expect(getPathFromState<object>(state)).toBe(path)
161
+ expect(getPathFromState<object>(getStateFromPath<object>(path) as State)).toBe(path)
162
+ })
163
+
164
+ test("doesn't add query param for empty params", () => {
165
+ const path = '/foo'
166
+ const state = {
167
+ routes: [
168
+ {
169
+ name: 'foo',
170
+ params: {},
171
+ },
172
+ ],
173
+ }
174
+
175
+ expect(getPathFromState<object>(state)).toBe(path)
176
+ expect(getPathFromState<object>(getStateFromPath<object>(path) as State)).toBe(path)
177
+ })
178
+
179
+ test('handles state with config with nested screens', () => {
180
+ const path = '/foo/foe/bar/sweet/apple/baz/jane?answer=42&count=10&valid=true'
181
+ const config = {
182
+ screens: {
183
+ Foo: {
184
+ path: 'foo',
185
+ screens: {
186
+ Foe: {
187
+ path: 'foe',
188
+ screens: {
189
+ Bar: {
190
+ path: 'bar/:type/:fruit',
191
+ screens: {
192
+ Baz: {
193
+ path: 'baz/:author',
194
+ parse: {
195
+ author: (author: string) =>
196
+ author.replace(/^\w/, (c) => c.toUpperCase()),
197
+ count: Number,
198
+ valid: Boolean,
199
+ },
200
+ stringify: {
201
+ author: (author: string) => author.toLowerCase(),
202
+ id: (id: number) => `x${id}`,
203
+ unknown: (_: unknown) => 'x',
204
+ },
205
+ },
206
+ },
207
+ },
208
+ },
209
+ },
210
+ },
211
+ },
212
+ },
213
+ }
214
+
215
+ const state = {
216
+ routes: [
217
+ {
218
+ name: 'Foo',
219
+ state: {
220
+ routes: [
221
+ {
222
+ name: 'Foe',
223
+ state: {
224
+ routes: [
225
+ {
226
+ name: 'Bar',
227
+ params: { fruit: 'apple', type: 'sweet' },
228
+ state: {
229
+ routes: [
230
+ {
231
+ name: 'Baz',
232
+ params: {
233
+ answer: '42',
234
+ author: 'Jane',
235
+ count: '10',
236
+ valid: true,
237
+ },
238
+ },
239
+ ],
240
+ },
241
+ },
242
+ ],
243
+ },
244
+ },
245
+ ],
246
+ },
247
+ },
248
+ ],
249
+ }
250
+
251
+ expect(getPathFromState<object>(state, config)).toBe(path)
252
+ expect(
253
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
254
+ ).toBe(path)
255
+ })
256
+
257
+ test('handles state with config with nested screens and exact', () => {
258
+ const path = '/foe/bar/sweet/apple/baz/jane?answer=42&count=10&valid=true'
259
+ const config = {
260
+ screens: {
261
+ Foo: {
262
+ path: 'foo',
263
+ screens: {
264
+ Foe: {
265
+ path: 'foe',
266
+ exact: true,
267
+ screens: {
268
+ Bar: {
269
+ path: 'bar/:type/:fruit',
270
+ screens: {
271
+ Baz: {
272
+ path: 'baz/:author',
273
+ parse: {
274
+ author: (author: string) =>
275
+ author.replace(/^\w/, (c) => c.toUpperCase()),
276
+ count: Number,
277
+ valid: Boolean,
278
+ },
279
+ stringify: {
280
+ author: (author: string) => author.toLowerCase(),
281
+ id: (id: number) => `x${id}`,
282
+ unknown: (_: unknown) => 'x',
283
+ },
284
+ },
285
+ },
286
+ },
287
+ },
288
+ },
289
+ },
290
+ },
291
+ },
292
+ }
293
+
294
+ const state = {
295
+ routes: [
296
+ {
297
+ name: 'Foo',
298
+ state: {
299
+ routes: [
300
+ {
301
+ name: 'Foe',
302
+ state: {
303
+ routes: [
304
+ {
305
+ name: 'Bar',
306
+ params: { fruit: 'apple', type: 'sweet' },
307
+ state: {
308
+ routes: [
309
+ {
310
+ name: 'Baz',
311
+ params: {
312
+ answer: '42',
313
+ author: 'Jane',
314
+ count: '10',
315
+ valid: true,
316
+ },
317
+ },
318
+ ],
319
+ },
320
+ },
321
+ ],
322
+ },
323
+ },
324
+ ],
325
+ },
326
+ },
327
+ ],
328
+ }
329
+
330
+ expect(getPathFromState<object>(state, config)).toBe(path)
331
+ expect(
332
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
333
+ ).toBe(path)
334
+ })
335
+
336
+ test('handles state with config with nested screens and unused configs', () => {
337
+ const path = '/foo/foe/baz/jane?answer=42&count=10&valid=true'
338
+ const config = {
339
+ screens: {
340
+ Foo: {
341
+ path: 'foo',
342
+ screens: {
343
+ Foe: {
344
+ path: 'foe',
345
+ screens: {
346
+ Baz: {
347
+ path: 'baz/:author',
348
+ parse: {
349
+ author: (author: string) =>
350
+ author.replace(/^\w/, (c) => c.toUpperCase()),
351
+ count: Number,
352
+ valid: Boolean,
353
+ },
354
+ stringify: {
355
+ author: (author: string) =>
356
+ author.replace(/^\w/, (c) => c.toLowerCase()),
357
+ unknown: (_: unknown) => 'x',
358
+ },
359
+ },
360
+ },
361
+ },
362
+ },
363
+ },
364
+ },
365
+ }
366
+
367
+ const state = {
368
+ routes: [
369
+ {
370
+ name: 'Foo',
371
+ state: {
372
+ routes: [
373
+ {
374
+ name: 'Foe',
375
+ state: {
376
+ routes: [
377
+ {
378
+ name: 'Baz',
379
+ params: {
380
+ answer: '42',
381
+ author: 'Jane',
382
+ count: 10,
383
+ valid: true,
384
+ },
385
+ },
386
+ ],
387
+ },
388
+ },
389
+ ],
390
+ },
391
+ },
392
+ ],
393
+ }
394
+
395
+ expect(getPathFromState<object>(state, config)).toBe(path)
396
+ expect(
397
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
398
+ ).toBe(path)
399
+ })
400
+
401
+ test('handles state with config with nested screens and unused configs with exact', () => {
402
+ const path = '/foe/baz/jane?answer=42&count=10&valid=true'
403
+ const config = {
404
+ screens: {
405
+ Foo: {
406
+ path: 'foo',
407
+ screens: {
408
+ Foe: {
409
+ path: 'foe',
410
+ exact: true,
411
+ screens: {
412
+ Baz: {
413
+ path: 'baz/:author',
414
+ parse: {
415
+ author: (author: string) =>
416
+ author.replace(/^\w/, (c) => c.toUpperCase()),
417
+ count: Number,
418
+ valid: Boolean,
419
+ },
420
+ stringify: {
421
+ author: (author: string) =>
422
+ author.replace(/^\w/, (c) => c.toLowerCase()),
423
+ unknown: (_: unknown) => 'x',
424
+ },
425
+ },
426
+ },
427
+ },
428
+ },
429
+ },
430
+ },
431
+ }
432
+
433
+ const state = {
434
+ routes: [
435
+ {
436
+ name: 'Foo',
437
+ state: {
438
+ routes: [
439
+ {
440
+ name: 'Foe',
441
+ state: {
442
+ routes: [
443
+ {
444
+ name: 'Baz',
445
+ params: {
446
+ answer: '42',
447
+ author: 'Jane',
448
+ count: 10,
449
+ valid: true,
450
+ },
451
+ },
452
+ ],
453
+ },
454
+ },
455
+ ],
456
+ },
457
+ },
458
+ ],
459
+ }
460
+
461
+ expect(getPathFromState<object>(state, config)).toBe(path)
462
+ expect(
463
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
464
+ ).toBe(path)
465
+ })
466
+
467
+ test('handles nested object with stringify in it', () => {
468
+ const path = '/bar/sweet/apple/foo/bis/jane?answer=42&count=10&valid=true'
469
+ const config = {
470
+ screens: {
471
+ Bar: {
472
+ path: 'bar/:type/:fruit',
473
+ screens: {
474
+ Foo: {
475
+ path: 'foo',
476
+ screens: {
477
+ Foe: {
478
+ path: 'foe',
479
+ },
480
+ Baz: {
481
+ screens: {
482
+ Bos: 'bos',
483
+ Bis: {
484
+ path: 'bis/:author',
485
+ stringify: {
486
+ author: (author: string) =>
487
+ author.replace(/^\w/, (c) => c.toLowerCase()),
488
+ },
489
+ parse: {
490
+ author: (author: string) =>
491
+ author.replace(/^\w/, (c) => c.toUpperCase()),
492
+ count: Number,
493
+ valid: Boolean,
494
+ },
495
+ },
496
+ },
497
+ },
498
+ },
499
+ },
500
+ },
501
+ },
502
+ },
503
+ }
504
+
505
+ const state = {
506
+ routes: [
507
+ {
508
+ name: 'Bar',
509
+ params: { fruit: 'apple', type: 'sweet' },
510
+ state: {
511
+ routes: [
512
+ {
513
+ name: 'Foo',
514
+ state: {
515
+ routes: [
516
+ {
517
+ name: 'Baz',
518
+ state: {
519
+ routes: [
520
+ {
521
+ name: 'Bis',
522
+ params: {
523
+ answer: '42',
524
+ author: 'Jane',
525
+ count: 10,
526
+ valid: true,
527
+ },
528
+ },
529
+ ],
530
+ },
531
+ },
532
+ ],
533
+ },
534
+ },
535
+ ],
536
+ },
537
+ },
538
+ ],
539
+ }
540
+
541
+ expect(getPathFromState<object>(state, config)).toBe(path)
542
+ expect(
543
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
544
+ ).toBe(path)
545
+ })
546
+
547
+ test('handles nested object with stringify in it with exact', () => {
548
+ const path = '/bis/jane?answer=42&count=10&valid=true'
549
+ const config = {
550
+ screens: {
551
+ Bar: {
552
+ path: 'bar/:type/:fruit',
553
+ screens: {
554
+ Foo: {
555
+ path: 'foo',
556
+ screens: {
557
+ Foe: {
558
+ path: 'foe',
559
+ },
560
+ Baz: {
561
+ path: 'baz',
562
+ screens: {
563
+ Bos: 'bos',
564
+ Bis: {
565
+ path: 'bis/:author',
566
+ exact: true,
567
+ stringify: {
568
+ author: (author: string) =>
569
+ author.replace(/^\w/, (c) => c.toLowerCase()),
570
+ },
571
+ parse: {
572
+ author: (author: string) =>
573
+ author.replace(/^\w/, (c) => c.toUpperCase()),
574
+ count: Number,
575
+ valid: Boolean,
576
+ },
577
+ },
578
+ },
579
+ },
580
+ },
581
+ },
582
+ },
583
+ },
584
+ },
585
+ }
586
+
587
+ const state = {
588
+ routes: [
589
+ {
590
+ name: 'Bar',
591
+ params: { fruit: 'apple', type: 'sweet' },
592
+ state: {
593
+ routes: [
594
+ {
595
+ name: 'Foo',
596
+ state: {
597
+ routes: [
598
+ {
599
+ name: 'Baz',
600
+ state: {
601
+ routes: [
602
+ {
603
+ name: 'Bis',
604
+ params: {
605
+ answer: '42',
606
+ author: 'Jane',
607
+ count: 10,
608
+ valid: true,
609
+ },
610
+ },
611
+ ],
612
+ },
613
+ },
614
+ ],
615
+ },
616
+ },
617
+ ],
618
+ },
619
+ },
620
+ ],
621
+ }
622
+
623
+ expect(getPathFromState<object>(state, config)).toBe(path)
624
+ expect(
625
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
626
+ ).toBe(path)
627
+ })
628
+
629
+ test('handles nested object for second route depth', () => {
630
+ const path = '/foo/bar/baz'
631
+ const config = {
632
+ screens: {
633
+ Foo: {
634
+ path: 'foo',
635
+ screens: {
636
+ Foe: 'foe',
637
+ Bar: {
638
+ path: 'bar',
639
+ screens: {
640
+ Baz: 'baz',
641
+ },
642
+ },
643
+ },
644
+ },
645
+ },
646
+ }
647
+
648
+ const state = {
649
+ routes: [
650
+ {
651
+ name: 'Foo',
652
+ state: {
653
+ routes: [
654
+ {
655
+ name: 'Bar',
656
+ state: {
657
+ routes: [{ name: 'Baz' }],
658
+ },
659
+ },
660
+ ],
661
+ },
662
+ },
663
+ ],
664
+ }
665
+
666
+ expect(getPathFromState<object>(state, config)).toBe(path)
667
+ expect(
668
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
669
+ ).toBe(path)
670
+ })
671
+
672
+ test('handles nested object for second route depth with exact', () => {
673
+ const path = '/baz'
674
+ const config = {
675
+ screens: {
676
+ Foo: {
677
+ path: 'foo',
678
+ screens: {
679
+ Foe: 'foe',
680
+ Bar: {
681
+ path: 'bar',
682
+ screens: {
683
+ Baz: {
684
+ path: 'baz',
685
+ exact: true,
686
+ },
687
+ },
688
+ },
689
+ },
690
+ },
691
+ },
692
+ }
693
+
694
+ const state = {
695
+ routes: [
696
+ {
697
+ name: 'Foo',
698
+ state: {
699
+ routes: [
700
+ {
701
+ name: 'Bar',
702
+ state: {
703
+ routes: [{ name: 'Baz' }],
704
+ },
705
+ },
706
+ ],
707
+ },
708
+ },
709
+ ],
710
+ }
711
+
712
+ expect(getPathFromState<object>(state, config)).toBe(path)
713
+ expect(
714
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
715
+ ).toBe(path)
716
+ })
717
+
718
+ test('handles nested object for second route depth and path and stringify in roots', () => {
719
+ const path = '/foo/dathomir/bar/42/baz'
720
+ const config = {
721
+ screens: {
722
+ Foo: {
723
+ path: 'foo/:planet',
724
+ stringify: {
725
+ id: (id: number) => `planet=${id}`,
726
+ },
727
+ screens: {
728
+ Foe: 'foe',
729
+ Bar: {
730
+ path: 'bar/:id',
731
+ parse: {
732
+ id: Number,
733
+ },
734
+ screens: {
735
+ Baz: 'baz',
736
+ },
737
+ },
738
+ },
739
+ },
740
+ },
741
+ }
742
+
743
+ const state = {
744
+ routes: [
745
+ {
746
+ name: 'Foo',
747
+ params: { planet: 'dathomir' },
748
+ state: {
749
+ routes: [
750
+ {
751
+ name: 'Bar',
752
+ state: {
753
+ routes: [{ name: 'Baz', params: { id: 42 } }],
754
+ },
755
+ },
756
+ ],
757
+ },
758
+ },
759
+ ],
760
+ }
761
+
762
+ expect(getPathFromState<object>(state, config)).toBe(path)
763
+ expect(
764
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
765
+ ).toBe(path)
766
+ })
767
+
768
+ test('handles nested object for second route depth and path and stringify in roots with exact', () => {
769
+ const path = '/baz'
770
+ const config = {
771
+ screens: {
772
+ Foo: {
773
+ path: 'foo/:id',
774
+ stringify: {
775
+ id: (id: number) => `id=${id}`,
776
+ },
777
+ screens: {
778
+ Foe: 'foe',
779
+ Bar: {
780
+ path: 'bar/:id',
781
+ stringify: {
782
+ id: (id: number) => `id=${id}`,
783
+ },
784
+ parse: {
785
+ id: Number,
786
+ },
787
+ screens: {
788
+ Baz: {
789
+ path: 'baz',
790
+ exact: true,
791
+ },
792
+ },
793
+ },
794
+ },
795
+ },
796
+ },
797
+ }
798
+
799
+ const state = {
800
+ routes: [
801
+ {
802
+ name: 'Foo',
803
+ state: {
804
+ routes: [
805
+ {
806
+ name: 'Bar',
807
+ state: {
808
+ routes: [{ name: 'Baz' }],
809
+ },
810
+ },
811
+ ],
812
+ },
813
+ },
814
+ ],
815
+ }
816
+
817
+ expect(getPathFromState<object>(state, config)).toBe(path)
818
+ expect(
819
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
820
+ ).toBe(path)
821
+ })
822
+
823
+ test('ignores empty string paths', () => {
824
+ const path = '/bar'
825
+ const config = {
826
+ screens: {
827
+ Foo: {
828
+ path: '',
829
+ screens: {
830
+ Foe: 'foe',
831
+ },
832
+ },
833
+ Bar: 'bar',
834
+ },
835
+ }
836
+
837
+ const state = {
838
+ routes: [
839
+ {
840
+ name: 'Foo',
841
+ state: {
842
+ routes: [{ name: 'Bar' }],
843
+ },
844
+ },
845
+ ],
846
+ }
847
+
848
+ expect(getPathFromState<object>(state, config)).toBe(path)
849
+ expect(
850
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
851
+ ).toBe(path)
852
+ })
853
+
854
+ test('keeps query params if path is empty', () => {
855
+ const path = '/?foo=42'
856
+ const config = {
857
+ screens: {
858
+ Foo: {
859
+ screens: {
860
+ Foe: 'foe',
861
+ Bar: {
862
+ screens: {
863
+ Qux: {
864
+ path: '',
865
+ parse: { foo: Number },
866
+ },
867
+ Baz: 'baz',
868
+ },
869
+ },
870
+ },
871
+ },
872
+ },
873
+ }
874
+
875
+ const state = {
876
+ routes: [
877
+ {
878
+ name: 'Foo',
879
+ state: {
880
+ routes: [
881
+ {
882
+ name: 'Bar',
883
+ state: {
884
+ routes: [{ name: 'Qux', params: { foo: 42 } }],
885
+ },
886
+ },
887
+ ],
888
+ },
889
+ },
890
+ ],
891
+ }
892
+
893
+ expect(getPathFromState<object>(state, config)).toBe(path)
894
+ expect(
895
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
896
+ ).toEqual(path)
897
+ })
898
+
899
+ test('does not use Object.prototype properties as parsing functions', () => {
900
+ const path = '/?toString=42'
901
+ const config = {
902
+ screens: {
903
+ Foo: {
904
+ screens: {
905
+ Foe: 'foe',
906
+ Bar: {
907
+ screens: {
908
+ Qux: {
909
+ path: '',
910
+ parse: {},
911
+ },
912
+ Baz: 'baz',
913
+ },
914
+ },
915
+ },
916
+ },
917
+ },
918
+ }
919
+
920
+ const state = {
921
+ routes: [
922
+ {
923
+ name: 'Foo',
924
+ state: {
925
+ routes: [
926
+ {
927
+ name: 'Bar',
928
+ state: {
929
+ routes: [{ name: 'Qux', params: { toString: 42 } }],
930
+ },
931
+ },
932
+ ],
933
+ },
934
+ },
935
+ ],
936
+ }
937
+
938
+ expect(getPathFromState<object>(state, config)).toBe(path)
939
+ expect(
940
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
941
+ ).toEqual(path)
942
+ })
943
+
944
+ test('cuts nested configs too', () => {
945
+ const path = '/foo/baz'
946
+ const config = {
947
+ screens: {
948
+ Foo: {
949
+ path: 'foo',
950
+ screens: {
951
+ Bar: {
952
+ path: '',
953
+ screens: {
954
+ Baz: {
955
+ path: 'baz',
956
+ },
957
+ },
958
+ },
959
+ },
960
+ },
961
+ },
962
+ }
963
+
964
+ const state = {
965
+ routes: [
966
+ {
967
+ name: 'Foo',
968
+ state: {
969
+ routes: [
970
+ {
971
+ name: 'Bar',
972
+ state: {
973
+ routes: [{ name: 'Baz' }],
974
+ },
975
+ },
976
+ ],
977
+ },
978
+ },
979
+ ],
980
+ }
981
+
982
+ expect(getPathFromState<object>(state, config)).toBe(path)
983
+ expect(
984
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
985
+ ).toBe(path)
986
+ })
987
+
988
+ test('cuts nested configs too with exact', () => {
989
+ const path = '/baz'
990
+ const config = {
991
+ screens: {
992
+ Foo: {
993
+ path: 'foo',
994
+ screens: {
995
+ Bar: {
996
+ path: '',
997
+ exact: true,
998
+ screens: {
999
+ Baz: {
1000
+ path: 'baz',
1001
+ },
1002
+ },
1003
+ },
1004
+ },
1005
+ },
1006
+ },
1007
+ }
1008
+
1009
+ const state = {
1010
+ routes: [
1011
+ {
1012
+ name: 'Foo',
1013
+ state: {
1014
+ routes: [
1015
+ {
1016
+ name: 'Bar',
1017
+ state: {
1018
+ routes: [{ name: 'Baz' }],
1019
+ },
1020
+ },
1021
+ ],
1022
+ },
1023
+ },
1024
+ ],
1025
+ }
1026
+
1027
+ expect(getPathFromState<object>(state, config)).toBe(path)
1028
+ expect(
1029
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
1030
+ ).toBe(path)
1031
+ })
1032
+
1033
+ test('handles empty path at the end', () => {
1034
+ const path = '/foo/bar'
1035
+ const config = {
1036
+ screens: {
1037
+ Foo: {
1038
+ path: 'foo',
1039
+ screens: {
1040
+ Bar: 'bar',
1041
+ },
1042
+ },
1043
+ Baz: { path: '' },
1044
+ },
1045
+ }
1046
+
1047
+ const state = {
1048
+ routes: [
1049
+ {
1050
+ name: 'Foo',
1051
+ state: {
1052
+ routes: [
1053
+ {
1054
+ name: 'Bar',
1055
+ state: {
1056
+ routes: [{ name: 'Baz' }],
1057
+ },
1058
+ },
1059
+ ],
1060
+ },
1061
+ },
1062
+ ],
1063
+ }
1064
+
1065
+ expect(getPathFromState<object>(state, config)).toBe(path)
1066
+ expect(
1067
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
1068
+ ).toBe(path)
1069
+ })
1070
+
1071
+ test('returns "/" for empty path', () => {
1072
+ const path = '/'
1073
+
1074
+ const config = {
1075
+ screens: {
1076
+ Foo: {
1077
+ path: '',
1078
+ screens: {
1079
+ Bar: '',
1080
+ },
1081
+ },
1082
+ },
1083
+ }
1084
+
1085
+ const state = {
1086
+ routes: [
1087
+ {
1088
+ name: 'Foo',
1089
+ state: {
1090
+ routes: [
1091
+ {
1092
+ name: 'Bar',
1093
+ },
1094
+ ],
1095
+ },
1096
+ },
1097
+ ],
1098
+ }
1099
+
1100
+ expect(getPathFromState<object>(state, config)).toBe(path)
1101
+ expect(
1102
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
1103
+ ).toBe(path)
1104
+ })
1105
+
1106
+ test('parses no path specified', () => {
1107
+ const path = '/bar'
1108
+ const config = {
1109
+ screens: {
1110
+ Foo: {
1111
+ screens: {
1112
+ Foe: {},
1113
+ Bar: 'bar',
1114
+ },
1115
+ },
1116
+ },
1117
+ }
1118
+
1119
+ const state = {
1120
+ routes: [
1121
+ {
1122
+ name: 'Foo',
1123
+ state: {
1124
+ routes: [{ name: 'Bar' }],
1125
+ },
1126
+ },
1127
+ ],
1128
+ }
1129
+
1130
+ expect(getPathFromState<object>(state, config)).toBe(path)
1131
+ expect(
1132
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
1133
+ ).toBe(path)
1134
+ })
1135
+
1136
+ test('strips undefined query params', () => {
1137
+ const path = '/bar/sweet/apple/foo/bis/jane?count=10&valid=true'
1138
+ const config = {
1139
+ screens: {
1140
+ Bar: {
1141
+ path: 'bar/:type/:fruit',
1142
+ screens: {
1143
+ Foo: {
1144
+ path: 'foo',
1145
+ screens: {
1146
+ Foe: {
1147
+ path: 'foe',
1148
+ },
1149
+ Baz: {
1150
+ screens: {
1151
+ Bos: 'bos',
1152
+ Bis: {
1153
+ path: 'bis/:author',
1154
+ stringify: {
1155
+ author: (author: string) =>
1156
+ author.replace(/^\w/, (c) => c.toLowerCase()),
1157
+ },
1158
+ parse: {
1159
+ author: (author: string) =>
1160
+ author.replace(/^\w/, (c) => c.toUpperCase()),
1161
+ count: Number,
1162
+ valid: Boolean,
1163
+ },
1164
+ },
1165
+ },
1166
+ },
1167
+ },
1168
+ },
1169
+ },
1170
+ },
1171
+ },
1172
+ }
1173
+
1174
+ const state = {
1175
+ routes: [
1176
+ {
1177
+ name: 'Bar',
1178
+ params: { fruit: 'apple', type: 'sweet' },
1179
+ state: {
1180
+ routes: [
1181
+ {
1182
+ name: 'Foo',
1183
+ state: {
1184
+ routes: [
1185
+ {
1186
+ name: 'Baz',
1187
+ state: {
1188
+ routes: [
1189
+ {
1190
+ name: 'Bis',
1191
+ params: {
1192
+ author: 'Jane',
1193
+ count: 10,
1194
+ valid: true,
1195
+ },
1196
+ },
1197
+ ],
1198
+ },
1199
+ },
1200
+ ],
1201
+ },
1202
+ },
1203
+ ],
1204
+ },
1205
+ },
1206
+ ],
1207
+ }
1208
+
1209
+ expect(getPathFromState<object>(state, config)).toBe(path)
1210
+ expect(
1211
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
1212
+ ).toBe(path)
1213
+ })
1214
+
1215
+ test('strips undefined query params with exact', () => {
1216
+ const path = '/bis/jane?count=10&valid=true'
1217
+ const config = {
1218
+ screens: {
1219
+ Bar: {
1220
+ path: 'bar/:type/:fruit',
1221
+ screens: {
1222
+ Foo: {
1223
+ path: 'foo',
1224
+ screens: {
1225
+ Foe: {
1226
+ path: 'foe',
1227
+ },
1228
+ Baz: {
1229
+ screens: {
1230
+ Bos: 'bos',
1231
+ Bis: {
1232
+ path: 'bis/:author',
1233
+ exact: true,
1234
+ stringify: {
1235
+ author: (author: string) =>
1236
+ author.replace(/^\w/, (c) => c.toLowerCase()),
1237
+ },
1238
+ parse: {
1239
+ author: (author: string) =>
1240
+ author.replace(/^\w/, (c) => c.toUpperCase()),
1241
+ count: Number,
1242
+ valid: Boolean,
1243
+ },
1244
+ },
1245
+ },
1246
+ },
1247
+ },
1248
+ },
1249
+ },
1250
+ },
1251
+ },
1252
+ }
1253
+
1254
+ const state = {
1255
+ routes: [
1256
+ {
1257
+ name: 'Bar',
1258
+ params: { fruit: 'apple', type: 'sweet' },
1259
+ state: {
1260
+ routes: [
1261
+ {
1262
+ name: 'Foo',
1263
+ state: {
1264
+ routes: [
1265
+ {
1266
+ name: 'Baz',
1267
+ state: {
1268
+ routes: [
1269
+ {
1270
+ name: 'Bis',
1271
+ params: {
1272
+ author: 'Jane',
1273
+ count: 10,
1274
+ valid: true,
1275
+ },
1276
+ },
1277
+ ],
1278
+ },
1279
+ },
1280
+ ],
1281
+ },
1282
+ },
1283
+ ],
1284
+ },
1285
+ },
1286
+ ],
1287
+ }
1288
+
1289
+ expect(getPathFromState<object>(state, config)).toBe(path)
1290
+ expect(
1291
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
1292
+ ).toBe(path)
1293
+ })
1294
+
1295
+ test('handles stripping all query params', () => {
1296
+ const path = '/bar/sweet/apple/foo/bis/jane'
1297
+ const config = {
1298
+ screens: {
1299
+ Bar: {
1300
+ path: 'bar/:type/:fruit',
1301
+ screens: {
1302
+ Foo: {
1303
+ path: 'foo',
1304
+ screens: {
1305
+ Foe: {
1306
+ path: 'foe',
1307
+ },
1308
+ Baz: {
1309
+ screens: {
1310
+ Bos: 'bos',
1311
+ Bis: {
1312
+ path: 'bis/:author',
1313
+ stringify: {
1314
+ author: (author: string) =>
1315
+ author.replace(/^\w/, (c) => c.toLowerCase()),
1316
+ },
1317
+ parse: {
1318
+ author: (author: string) =>
1319
+ author.replace(/^\w/, (c) => c.toUpperCase()),
1320
+ count: Number,
1321
+ valid: Boolean,
1322
+ },
1323
+ },
1324
+ },
1325
+ },
1326
+ },
1327
+ },
1328
+ },
1329
+ },
1330
+ },
1331
+ }
1332
+
1333
+ const state = {
1334
+ routes: [
1335
+ {
1336
+ name: 'Bar',
1337
+ params: { fruit: 'apple', type: 'sweet' },
1338
+ state: {
1339
+ routes: [
1340
+ {
1341
+ name: 'Foo',
1342
+ state: {
1343
+ routes: [
1344
+ {
1345
+ name: 'Baz',
1346
+ state: {
1347
+ routes: [
1348
+ {
1349
+ name: 'Bis',
1350
+ params: {
1351
+ author: 'Jane',
1352
+ },
1353
+ },
1354
+ ],
1355
+ },
1356
+ },
1357
+ ],
1358
+ },
1359
+ },
1360
+ ],
1361
+ },
1362
+ },
1363
+ ],
1364
+ }
1365
+
1366
+ expect(getPathFromState<object>(state, config)).toBe(path)
1367
+ expect(
1368
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
1369
+ ).toBe(path)
1370
+ })
1371
+
1372
+ test('handles stripping all query params with exact', () => {
1373
+ const path = '/bis/jane'
1374
+ const config = {
1375
+ screens: {
1376
+ Bar: {
1377
+ path: 'bar/:type/:fruit',
1378
+ screens: {
1379
+ Foo: {
1380
+ path: 'foo',
1381
+ screens: {
1382
+ Foe: {
1383
+ path: 'foe',
1384
+ },
1385
+ Baz: {
1386
+ path: 'baz',
1387
+ screens: {
1388
+ Bos: 'bos',
1389
+ Bis: {
1390
+ path: 'bis/:author',
1391
+ exact: true,
1392
+ stringify: {
1393
+ author: (author: string) =>
1394
+ author.replace(/^\w/, (c) => c.toLowerCase()),
1395
+ },
1396
+ parse: {
1397
+ author: (author: string) =>
1398
+ author.replace(/^\w/, (c) => c.toUpperCase()),
1399
+ count: Number,
1400
+ valid: Boolean,
1401
+ },
1402
+ },
1403
+ },
1404
+ },
1405
+ },
1406
+ },
1407
+ },
1408
+ },
1409
+ },
1410
+ }
1411
+
1412
+ const state = {
1413
+ routes: [
1414
+ {
1415
+ name: 'Bar',
1416
+ params: { fruit: 'apple', type: 'sweet' },
1417
+ state: {
1418
+ routes: [
1419
+ {
1420
+ name: 'Foo',
1421
+ state: {
1422
+ routes: [
1423
+ {
1424
+ name: 'Baz',
1425
+ state: {
1426
+ routes: [
1427
+ {
1428
+ name: 'Bis',
1429
+ params: {
1430
+ author: 'Jane',
1431
+ },
1432
+ },
1433
+ ],
1434
+ },
1435
+ },
1436
+ ],
1437
+ },
1438
+ },
1439
+ ],
1440
+ },
1441
+ },
1442
+ ],
1443
+ }
1444
+
1445
+ expect(getPathFromState<object>(state, config)).toBe(path)
1446
+ expect(
1447
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
1448
+ ).toBe(path)
1449
+ })
1450
+
1451
+ test('replaces undefined query params', () => {
1452
+ const path = '/bar/undefined/apple'
1453
+ const config = {
1454
+ screens: {
1455
+ Bar: 'bar/:type/:fruit',
1456
+ },
1457
+ }
1458
+
1459
+ const state = {
1460
+ routes: [
1461
+ {
1462
+ name: 'Bar',
1463
+ params: { fruit: 'apple' },
1464
+ },
1465
+ ],
1466
+ }
1467
+
1468
+ expect(getPathFromState<object>(state, config)).toBe(path)
1469
+ expect(
1470
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
1471
+ ).toBe(path)
1472
+ })
1473
+
1474
+ // @modify: TODO: temporally disable failing test
1475
+ test.skip('matches wildcard patterns at root', () => {
1476
+ const path = '/test/bar/42/whatever'
1477
+ const config = {
1478
+ screens: {
1479
+ 404: '*',
1480
+ Foo: {
1481
+ screens: {
1482
+ Bar: {
1483
+ path: '/bar/:id/',
1484
+ },
1485
+ },
1486
+ },
1487
+ },
1488
+ }
1489
+
1490
+ const state = {
1491
+ routes: [{ name: '404' }],
1492
+ }
1493
+
1494
+ expect(getPathFromState<object>(state, config)).toBe('/404')
1495
+ expect(
1496
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
1497
+ ).toBe('/404')
1498
+ })
1499
+
1500
+ // @modify: TODO: temporally disable failing test
1501
+ test.skip('matches wildcard patterns at nested level', () => {
1502
+ const path = '/bar/42/whatever/baz/initt'
1503
+ const config = {
1504
+ screens: {
1505
+ Foo: {
1506
+ screens: {
1507
+ Bar: {
1508
+ path: '/bar/:id/',
1509
+ screens: {
1510
+ 404: '*',
1511
+ },
1512
+ },
1513
+ },
1514
+ },
1515
+ },
1516
+ }
1517
+
1518
+ const state = {
1519
+ routes: [
1520
+ {
1521
+ name: 'Foo',
1522
+ state: {
1523
+ routes: [
1524
+ {
1525
+ name: 'Bar',
1526
+ params: { id: '42' },
1527
+ state: {
1528
+ routes: [{ name: '404' }],
1529
+ },
1530
+ },
1531
+ ],
1532
+ },
1533
+ },
1534
+ ],
1535
+ }
1536
+
1537
+ expect(getPathFromState<object>(state, config)).toBe('/bar/42/404')
1538
+ expect(
1539
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
1540
+ ).toBe('/bar/42/404')
1541
+ })
1542
+
1543
+ // @modify: TODO: temporally disable failing test
1544
+ test.skip('matches wildcard patterns at nested level with exact', () => {
1545
+ const path = '/whatever'
1546
+ const config = {
1547
+ screens: {
1548
+ Foo: {
1549
+ screens: {
1550
+ Bar: {
1551
+ path: '/bar/:id/',
1552
+ screens: {
1553
+ 404: {
1554
+ path: '*',
1555
+ exact: true,
1556
+ },
1557
+ },
1558
+ },
1559
+ Baz: {},
1560
+ },
1561
+ },
1562
+ },
1563
+ }
1564
+
1565
+ const state = {
1566
+ routes: [
1567
+ {
1568
+ name: 'Foo',
1569
+ state: {
1570
+ routes: [
1571
+ {
1572
+ name: 'Bar',
1573
+ state: {
1574
+ routes: [{ name: '404' }],
1575
+ },
1576
+ },
1577
+ ],
1578
+ },
1579
+ },
1580
+ ],
1581
+ }
1582
+
1583
+ expect(getPathFromState<object>(state, config)).toBe('/404')
1584
+ expect(
1585
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
1586
+ ).toBe('/404')
1587
+ })
1588
+
1589
+ test('tries to match wildcard patterns at the end', () => {
1590
+ const path = '/bar/42/test'
1591
+ const config = {
1592
+ screens: {
1593
+ Foo: {
1594
+ screens: {
1595
+ Bar: {
1596
+ path: '/bar/:id/',
1597
+ screens: {
1598
+ 404: '*',
1599
+ Test: 'test',
1600
+ },
1601
+ },
1602
+ },
1603
+ },
1604
+ },
1605
+ }
1606
+
1607
+ const state = {
1608
+ routes: [
1609
+ {
1610
+ name: 'Foo',
1611
+ state: {
1612
+ routes: [
1613
+ {
1614
+ name: 'Bar',
1615
+ params: { id: '42' },
1616
+ state: {
1617
+ routes: [{ name: 'Test' }],
1618
+ },
1619
+ },
1620
+ ],
1621
+ },
1622
+ },
1623
+ ],
1624
+ }
1625
+
1626
+ expect(getPathFromState<object>(state, config)).toBe(path)
1627
+ expect(
1628
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
1629
+ ).toBe(path)
1630
+ })
1631
+
1632
+ // @modify: TODO: temporally disable failing test
1633
+ test.skip('uses nearest parent wildcard match for unmatched paths', () => {
1634
+ const path = '/bar/42/baz/test'
1635
+ const config = {
1636
+ screens: {
1637
+ Foo: {
1638
+ screens: {
1639
+ Bar: {
1640
+ path: '/bar/:id/',
1641
+ screens: {
1642
+ Baz: 'baz',
1643
+ },
1644
+ },
1645
+ 404: '*',
1646
+ },
1647
+ },
1648
+ },
1649
+ }
1650
+
1651
+ const state = {
1652
+ routes: [
1653
+ {
1654
+ name: 'Foo',
1655
+ state: {
1656
+ routes: [{ name: '404' }],
1657
+ },
1658
+ },
1659
+ ],
1660
+ }
1661
+
1662
+ expect(getPathFromState<object>(state, config)).toBe('/404')
1663
+ expect(
1664
+ getPathFromState<object>(getStateFromPath<object>(path, config) as State, config)
1665
+ ).toBe('/404')
1666
+ })
1667
+
1668
+ // @modify: TODO: temporally disable failing test
1669
+ test.skip('handles path at top level', () => {
1670
+ const path = 'foo/fruits/apple'
1671
+ const config = {
1672
+ path: 'foo',
1673
+ screens: {
1674
+ Foo: {
1675
+ screens: {
1676
+ Fruits: 'fruits/:fruit',
1677
+ },
1678
+ },
1679
+ },
1680
+ }
1681
+
1682
+ const state = {
1683
+ routes: [
1684
+ {
1685
+ name: 'Foo',
1686
+ state: {
1687
+ routes: [
1688
+ {
1689
+ name: 'Fruits',
1690
+ params: { fruit: 'apple' },
1691
+ },
1692
+ ],
1693
+ },
1694
+ },
1695
+ ],
1696
+ }
1697
+
1698
+ expect(getPathFromState<object>(state, config)).toBe(`/${path}`)
1699
+ expect(getPathFromState<object>(getStateFromPath<object>(path, config)!, config)).toBe(
1700
+ `/${path}`
1701
+ )
1702
+ })
1703
+
1704
+ // @modify: TODO: temporally disable failing test
1705
+ test.skip('ignores regexp patterns when provided', () => {
1706
+ const config = {
1707
+ screens: {
1708
+ Foo: {
1709
+ path: 'foo/:id(\\d+)',
1710
+ parse: {
1711
+ id: Number,
1712
+ },
1713
+ },
1714
+ Bar: {
1715
+ path: 'foo/:id([a-z]+)',
1716
+ },
1717
+ Baz: {
1718
+ path: 'foo/:id(\\d+)/:name([a-z]+)',
1719
+ },
1720
+ Qux: {
1721
+ path: 'foo/:id(@[a-z]+)',
1722
+ stringify: {
1723
+ id: (id: string) => `@${id}`,
1724
+ },
1725
+ },
1726
+ },
1727
+ }
1728
+
1729
+ expect(
1730
+ getPathFromState<object>(
1731
+ {
1732
+ routes: [
1733
+ {
1734
+ name: 'Foo',
1735
+ params: { id: 42 },
1736
+ },
1737
+ ],
1738
+ },
1739
+ config
1740
+ )
1741
+ ).toBe('/foo/42')
1742
+
1743
+ expect(
1744
+ getPathFromState<object>(
1745
+ {
1746
+ routes: [
1747
+ {
1748
+ name: 'Bar',
1749
+ params: { id: 'bar' },
1750
+ },
1751
+ ],
1752
+ },
1753
+ config
1754
+ )
1755
+ ).toBe('/foo/bar')
1756
+
1757
+ expect(
1758
+ getPathFromState<object>(
1759
+ {
1760
+ routes: [
1761
+ {
1762
+ name: 'Baz',
1763
+ params: { id: 42, name: 'bar' },
1764
+ },
1765
+ ],
1766
+ },
1767
+ config
1768
+ )
1769
+ ).toBe('/foo/42/bar')
1770
+
1771
+ expect(
1772
+ getPathFromState<object>(
1773
+ {
1774
+ routes: [
1775
+ {
1776
+ name: 'Qux',
1777
+ params: { id: 'bar' },
1778
+ },
1779
+ ],
1780
+ },
1781
+ config
1782
+ )
1783
+ ).toBe('/foo/@bar')
1784
+ })
1785
+
1786
+ // @modify: TODO: temporally disable failing test
1787
+ test.skip('correctly handles regex pattern with slash', () => {
1788
+ const config = {
1789
+ screens: {
1790
+ Foo: {
1791
+ path: 'foo/:id([a-z]+\\/)',
1792
+ },
1793
+ },
1794
+ }
1795
+
1796
+ expect(
1797
+ getPathFromState<object>(
1798
+ {
1799
+ routes: [
1800
+ {
1801
+ name: 'Foo',
1802
+ params: { id: 'bar' },
1803
+ },
1804
+ ],
1805
+ },
1806
+ config
1807
+ )
1808
+ ).toBe('/foo/bar')
1809
+ })