1import Constants from 'expo-constants'; 2 3import { configFromFs } from '../../utils/mockState'; 4import getPathFromState from '../getPathFromState'; 5import getStateFromPath, { 6 stripBasePath, 7 getUrlWithReactNavigationConcessions, 8} from '../getStateFromPath'; 9 10jest.mock('expo-constants', () => ({ 11 __esModule: true, 12 default: { 13 expoConfig: {}, 14 }, 15})); 16 17afterEach(() => { 18 Constants.expoConfig!.experiments = undefined; 19}); 20 21describe(stripBasePath, () => { 22 [ 23 [ 24 // Input 25 '/', 26 // Base Path 27 '', 28 // Result 29 '/', 30 ], 31 ['/one/two', '/one', '/two'], 32 ['/one/two', '/one/two', ''], 33 ['/one/two/', '/one/two', '/'], 34 ['///one/', '/one', '/'], 35 ['one/', '/one', 'one/'], 36 ['/a/b', '/one', '/a/b'], 37 ].forEach(([path, basePath, result]) => { 38 it(`strips basePath "${path}"`, () => { 39 expect(stripBasePath(path, basePath)).toBe(result); 40 }); 41 }); 42}); 43 44describe('basePath', () => { 45 it('accounts for basePath', () => { 46 // @ts-expect-error 47 Constants.expoConfig = { 48 experiments: { 49 basePath: '/expo/prefix', 50 }, 51 }; 52 const path = '/expo/prefix/bar'; 53 const config = configFromFs(['_layout.tsx', 'bar.tsx', 'index.tsx']); 54 55 expect(getStateFromPath<object>(path, config)).toEqual({ 56 routes: [{ name: '', state: { routes: [{ name: 'bar', path: '/bar' }] } }], 57 }); 58 59 expect(getPathFromState(getStateFromPath<object>(path, config), config)).toBe( 60 '/expo/prefix/bar' 61 ); 62 }); 63 64 it('has basePath and state that does not match', () => { 65 // @ts-expect-error 66 Constants.expoConfig = { 67 experiments: { 68 basePath: '/expo', 69 }, 70 }; 71 const path = '/bar'; 72 const config = configFromFs(['_layout.tsx', 'bar.tsx', 'index.tsx']); 73 74 expect(getStateFromPath<object>(path, config)).toEqual({ 75 routes: [{ name: '', state: { routes: [{ name: 'bar', path: '/bar' }] } }], 76 }); 77 expect(getPathFromState(getStateFromPath<object>(path, config), config)).toBe('/expo/bar'); 78 }); 79}); 80 81describe(getUrlWithReactNavigationConcessions, () => { 82 ['/', 'foo/', 'foo/bar/', 'foo/bar/baz/'].forEach((path) => { 83 it(`returns the pathname for ${path}`, () => { 84 expect(getUrlWithReactNavigationConcessions(path).nonstandardPathname).toBe(path); 85 }); 86 }); 87 88 [ 89 ['', '/'], 90 ['https://acme.com/hello/world?foo=bar#123', 'hello/world/'], 91 ['https://acme.com/hello/world/?foo=bar#123', 'hello/world/'], 92 ].forEach(([url, expected]) => { 93 it(`returns the pathname for ${url}`, () => { 94 expect(getUrlWithReactNavigationConcessions(url).nonstandardPathname).toBe(expected); 95 }); 96 }); 97 98 [ 99 ['/gh-pages/', '/'], 100 ['https://acme.com/gh-pages/hello/world?foo=bar#123', 'hello/world/'], 101 ['https://acme.com/gh-pages/hello/world/?foo=bar#123', 'hello/world/'], 102 ].forEach(([url, expected]) => { 103 it(`returns the pathname for ${url}`, () => { 104 expect(getUrlWithReactNavigationConcessions(url, 'gh-pages').nonstandardPathname).toBe( 105 expected 106 ); 107 }); 108 }); 109 110 [ 111 ['', ''], 112 ['https://acme.com/hello/world/?foo=bar#123', 'https://acme.com/hello/world/?foo=bar'], 113 ['/foobar#123', '/foobar'], 114 ].forEach(([url, expected]) => { 115 it(`returns the pathname without hash for ${url}`, () => { 116 expect(getUrlWithReactNavigationConcessions(url).inputPathnameWithoutHash).toBe(expected); 117 }); 118 }); 119}); 120 121it(`strips hashes`, () => { 122 expect( 123 getStateFromPath('/hello#123', { 124 screens: { 125 hello: 'hello', 126 }, 127 } as any) 128 ).toEqual({ 129 routes: [ 130 { 131 name: 'hello', 132 path: '/hello', 133 }, 134 ], 135 }); 136 137 expect(getStateFromPath('/hello#123', configFromFs(['[hello].js']))).toEqual({ 138 routes: [ 139 { 140 name: '[hello]', 141 params: { 142 hello: 'hello', 143 }, 144 path: '/hello', 145 }, 146 ], 147 }); 148 149 // TODO: Test rest params 150}); 151 152it(`supports spaces`, () => { 153 expect( 154 getStateFromPath('/hello%20world', { 155 screens: { 156 'hello world': 'hello world', 157 }, 158 } as any) 159 ).toEqual({ 160 routes: [ 161 { 162 name: 'hello world', 163 path: '/hello%20world', 164 }, 165 ], 166 }); 167 168 expect(getStateFromPath('/hello%20world', configFromFs(['[hello world].js']))).toEqual({ 169 routes: [ 170 { 171 name: '[hello world]', 172 params: { 173 'hello world': 'hello%20world', 174 }, 175 path: '/hello%20world', 176 }, 177 ], 178 }); 179 180 // TODO: Test rest params 181}); 182 183it(`matches unmatched existing groups against 404`, () => { 184 expect( 185 getStateFromPath( 186 '/(app)/(explore)', 187 configFromFs([ 188 '[...404].js', 189 190 '(app)/_layout.tsx', 191 192 '(app)/(explore)/_layout.tsx', 193 '(app)/(explore)/[user]/index.tsx', 194 '(app)/(explore)/explore.tsx', 195 196 '(app)/([user])/_layout.tsx', 197 '(app)/([user])/[user]/index.tsx', 198 '(app)/([user])/explore.tsx', 199 ]) 200 ) 201 ).toEqual({ 202 routes: [ 203 { 204 name: '(app)', 205 params: { user: '(explore)' }, 206 state: { 207 routes: [ 208 { 209 name: '([user])', 210 params: { user: '(explore)' }, 211 state: { 212 routes: [ 213 { 214 name: '[user]/index', 215 params: { user: '(explore)' }, 216 path: '', 217 }, 218 ], 219 }, 220 }, 221 ], 222 }, 223 }, 224 ], 225 }); 226}); 227 228it(`adds dynamic route params from all levels of the path`, () => { 229 // A route at `app/[foo]/bar/[baz]/other` should get all of the params from the path. 230 expect( 231 getStateFromPath( 232 '/foo/bar/baz/other', 233 234 configFromFs([ 235 '[foo]/_layout.tsx', 236 '[foo]/bar/_layout.tsx', 237 '[foo]/bar/[baz]/_layout.tsx', 238 '[foo]/bar/[baz]/other.tsx', 239 ]) 240 ) 241 ).toEqual({ 242 routes: [ 243 { 244 name: '[foo]', 245 params: { baz: 'baz', foo: 'foo' }, 246 state: { 247 routes: [ 248 { 249 name: 'bar', 250 params: { baz: 'baz', foo: 'foo' }, 251 state: { 252 routes: [ 253 { 254 name: '[baz]', 255 params: { baz: 'baz', foo: 'foo' }, 256 state: { 257 routes: [ 258 { 259 name: 'other', 260 params: { 261 baz: 'baz', 262 foo: 'foo', 263 }, 264 path: '/foo/bar/baz/other', 265 }, 266 ], 267 }, 268 }, 269 ], 270 }, 271 }, 272 ], 273 }, 274 }, 275 ], 276 }); 277}); 278