1import { findFocusedRoute } from '@react-navigation/native'; 2import type { InitialState } from '@react-navigation/routers'; 3import produce from 'immer'; 4 5import { configFromFs } from '../../utils/mockState'; 6import getPathFromState from '../getPathFromState'; 7import getStateFromPath from '../getStateFromPath'; 8 9const changePath = <T extends InitialState>(state: T, path: string): T => 10 produce(state, (draftState) => { 11 const route = findFocusedRoute(draftState); 12 // @ts-expect-error: immer won't mutate this 13 route.path = path; 14 }); 15 16it('returns undefined for invalid path', () => { 17 expect(getStateFromPath<object>('//', { screens: {} })).toBe(undefined); 18}); 19 20it('converts path string to initial state with config', () => { 21 const path = '/foo/bar/sweet/apple/baz/jane?count=10&answer=42&valid=true'; 22 const config = { 23 screens: { 24 Foo: { 25 path: 'foo', 26 screens: { 27 Bar: { 28 path: 'bar/:type/:fruit', 29 screens: { 30 Baz: { 31 path: 'baz/:author', 32 parse: { 33 author: (author: string) => author.replace(/^\w/, (c) => c.toUpperCase()), 34 count: Number, 35 valid: Boolean, 36 }, 37 stringify: { 38 author: (author: string) => author.toLowerCase(), 39 }, 40 }, 41 }, 42 }, 43 }, 44 }, 45 }, 46 }; 47 48 const state = { 49 routes: [ 50 { 51 name: 'Foo', 52 params: { 53 author: 'Jane', 54 fruit: 'apple', 55 type: 'sweet', 56 }, 57 state: { 58 routes: [ 59 { 60 name: 'Bar', 61 params: { author: 'Jane', fruit: 'apple', type: 'sweet' }, 62 state: { 63 routes: [ 64 { 65 name: 'Baz', 66 params: { 67 author: 'Jane', 68 count: 10, 69 fruit: 'apple', 70 type: 'sweet', 71 answer: '42', 72 valid: true, 73 }, 74 path, 75 }, 76 ], 77 }, 78 }, 79 ], 80 }, 81 }, 82 ], 83 }; 84 85 testConversions(path, config, state); 86}); 87 88it('handles leading slash when converting', () => { 89 const path = '/foo/bar/?count=42'; 90 91 expect( 92 getStateFromPath<object>(path, { 93 screens: { 94 foo: { 95 path: 'foo', 96 screens: { 97 bar: { 98 path: 'bar', 99 }, 100 }, 101 }, 102 }, 103 }) 104 ).toEqual({ 105 routes: [ 106 { 107 name: 'foo', 108 state: { 109 routes: [ 110 { 111 name: 'bar', 112 params: { count: '42' }, 113 path, 114 }, 115 ], 116 }, 117 }, 118 ], 119 }); 120}); 121 122it('handles ending slash when converting', () => { 123 const path = 'foo/bar/?count=42'; 124 125 expect( 126 getStateFromPath<object>(path, { 127 screens: { 128 foo: { 129 path: 'foo', 130 screens: { 131 bar: { 132 path: 'bar', 133 }, 134 }, 135 }, 136 }, 137 }) 138 ).toEqual({ 139 routes: [ 140 { 141 name: 'foo', 142 state: { 143 routes: [ 144 { 145 name: 'bar', 146 params: { count: '42' }, 147 path, 148 }, 149 ], 150 }, 151 }, 152 ], 153 }); 154}); 155 156it('converts path string to initial state with config with nested screens', () => { 157 const path = '/foe/bar/sweet/apple/baz/jane?count=10&answer=42&valid=true'; 158 const config = { 159 screens: { 160 Foo: { 161 path: 'foo', 162 screens: { 163 Foe: { 164 path: 'foe', 165 exact: true, 166 screens: { 167 Bar: { 168 path: 'bar/:type/:fruit', 169 screens: { 170 Baz: { 171 path: 'baz/:author', 172 parse: { 173 author: (author: string) => author.replace(/^\w/, (c) => c.toUpperCase()), 174 count: Number, 175 valid: Boolean, 176 }, 177 stringify: { 178 author: (author: string) => author.toLowerCase(), 179 }, 180 }, 181 }, 182 }, 183 }, 184 }, 185 }, 186 }, 187 }, 188 }; 189 190 const state = { 191 routes: [ 192 { 193 name: 'Foo', 194 params: { 195 fruit: 'apple', 196 type: 'sweet', 197 author: 'Jane', 198 }, 199 state: { 200 routes: [ 201 { 202 name: 'Foe', 203 params: { 204 fruit: 'apple', 205 type: 'sweet', 206 author: 'Jane', 207 }, 208 state: { 209 routes: [ 210 { 211 name: 'Bar', 212 params: { 213 fruit: 'apple', 214 type: 'sweet', 215 author: 'Jane', 216 }, 217 state: { 218 routes: [ 219 { 220 name: 'Baz', 221 params: { 222 fruit: 'apple', 223 type: 'sweet', 224 author: 'Jane', 225 count: 10, 226 answer: '42', 227 valid: true, 228 }, 229 path, 230 }, 231 ], 232 }, 233 }, 234 ], 235 }, 236 }, 237 ], 238 }, 239 }, 240 ], 241 }; 242 243 testConversions(path, config, state); 244}); 245 246it('converts path string to initial state with config with nested screens and unused parse functions', () => { 247 const path = '/foe/baz/jane?count=10&answer=42&valid=true'; 248 const config = { 249 screens: { 250 Foo: { 251 path: 'foo', 252 screens: { 253 Foe: { 254 path: 'foe', 255 exact: true, 256 screens: { 257 Baz: { 258 path: 'baz/:author', 259 parse: { 260 author: (author: string) => author.replace(/^\w/, (c) => c.toUpperCase()), 261 count: Number, 262 valid: Boolean, 263 id: Boolean, 264 }, 265 }, 266 }, 267 }, 268 }, 269 }, 270 }, 271 }; 272 273 const state = { 274 routes: [ 275 { 276 name: 'Foo', 277 params: { 278 author: 'Jane', 279 }, 280 state: { 281 routes: [ 282 { 283 name: 'Foe', 284 params: { 285 author: 'Jane', 286 }, 287 state: { 288 routes: [ 289 { 290 name: 'Baz', 291 params: { 292 author: 'Jane', 293 count: 10, 294 answer: '42', 295 valid: true, 296 }, 297 path, 298 }, 299 ], 300 }, 301 }, 302 ], 303 }, 304 }, 305 ], 306 }; 307 308 expect(getStateFromPath<object>(path, config)).toEqual(state); 309 expect(getStateFromPath<object>(getPathFromState<object>(state, config), config)).toEqual( 310 changePath(state, '/foe/baz/Jane?count=10&answer=42&valid=true') 311 ); 312}); 313 314it('handles nested object with unused configs and with parse in it', () => { 315 const path = '/bar/sweet/apple/foe/bis/jane?count=10&answer=42&valid=true'; 316 const config = { 317 screens: { 318 Bar: { 319 path: 'bar/:type/:fruit', 320 screens: { 321 Foo: { 322 screens: { 323 Foe: { 324 path: 'foe', 325 screens: { 326 Baz: { 327 screens: { 328 Bos: { 329 path: 'bos', 330 exact: true, 331 }, 332 Bis: { 333 path: 'bis/:author', 334 stringify: { 335 author: (author: string) => author.replace(/^\w/, (c) => c.toLowerCase()), 336 }, 337 parse: { 338 author: (author: string) => author.replace(/^\w/, (c) => c.toUpperCase()), 339 count: Number, 340 valid: Boolean, 341 }, 342 }, 343 }, 344 }, 345 }, 346 }, 347 }, 348 }, 349 }, 350 }, 351 }, 352 }; 353 354 const state = { 355 routes: [ 356 { 357 name: 'Bar', 358 params: { fruit: 'apple', type: 'sweet', author: 'Jane' }, 359 state: { 360 routes: [ 361 { 362 name: 'Foo', 363 params: { 364 author: 'Jane', 365 fruit: 'apple', 366 type: 'sweet', 367 }, 368 state: { 369 routes: [ 370 { 371 name: 'Foe', 372 params: { 373 author: 'Jane', 374 fruit: 'apple', 375 type: 'sweet', 376 }, 377 state: { 378 routes: [ 379 { 380 name: 'Baz', 381 params: { 382 author: 'Jane', 383 fruit: 'apple', 384 type: 'sweet', 385 }, 386 state: { 387 routes: [ 388 { 389 name: 'Bis', 390 params: { 391 author: 'Jane', 392 count: 10, 393 answer: '42', 394 valid: true, 395 fruit: 'apple', 396 type: 'sweet', 397 }, 398 path, 399 }, 400 ], 401 }, 402 }, 403 ], 404 }, 405 }, 406 ], 407 }, 408 }, 409 ], 410 }, 411 }, 412 ], 413 }; 414 415 testConversions(path, config, state); 416}); 417 418it('handles parse in nested object for second route depth', () => { 419 const path = '/baz'; 420 const config = { 421 screens: { 422 Foo: { 423 path: 'foo', 424 screens: { 425 Foe: { 426 path: 'foe', 427 exact: true, 428 }, 429 Bar: { 430 path: 'bar', 431 exact: true, 432 screens: { 433 Baz: { 434 path: 'baz', 435 exact: true, 436 }, 437 }, 438 }, 439 }, 440 }, 441 }, 442 }; 443 444 const state = { 445 routes: [ 446 { 447 name: 'Foo', 448 state: { 449 routes: [ 450 { 451 name: 'Bar', 452 state: { 453 routes: [{ name: 'Baz', path }], 454 }, 455 }, 456 ], 457 }, 458 }, 459 ], 460 }; 461 462 testConversions(path, config, state); 463}); 464 465it('handles parse in nested object for second route depth and and path and parse in roots', () => { 466 const path = '/baz'; 467 const config = { 468 screens: { 469 Foo: { 470 path: 'foo/:id', 471 parse: { 472 id: Number, 473 }, 474 stringify: { 475 id: (id: number) => `id=${id}`, 476 }, 477 screens: { 478 Foe: 'foe', 479 Bar: { 480 screens: { 481 Baz: { 482 path: 'baz', 483 exact: true, 484 }, 485 }, 486 }, 487 }, 488 }, 489 }, 490 }; 491 492 const state = { 493 routes: [ 494 { 495 name: 'Foo', 496 state: { 497 routes: [ 498 { 499 name: 'Bar', 500 state: { 501 routes: [{ name: 'Baz', path }], 502 }, 503 }, 504 ], 505 }, 506 }, 507 ], 508 }; 509 510 testConversions(path, config, state); 511}); 512 513it('handles initialRouteName inside a screen', () => { 514 const path = '/baz'; 515 const config = { 516 screens: { 517 Foo: { 518 initialRouteName: 'Foe', 519 screens: { 520 Foe: 'foe', 521 Bar: { 522 screens: { 523 Baz: 'baz', 524 }, 525 }, 526 }, 527 }, 528 }, 529 }; 530 531 const state = { 532 routes: [ 533 { 534 name: 'Foo', 535 state: { 536 index: 1, 537 routes: [ 538 { 539 name: 'Foe', 540 }, 541 { 542 name: 'Bar', 543 state: { 544 routes: [{ name: 'Baz', path }], 545 }, 546 }, 547 ], 548 }, 549 }, 550 ], 551 }; 552 553 testConversions(path, config, state); 554}); 555 556it('handles initialRouteName included in path', () => { 557 const path = '/baz'; 558 const config = { 559 screens: { 560 Foo: { 561 initialRouteName: 'Foe', 562 screens: { 563 Foe: { 564 screens: { 565 Baz: 'baz', 566 }, 567 }, 568 Bar: 'bar', 569 }, 570 }, 571 }, 572 }; 573 574 const state = { 575 routes: [ 576 { 577 name: 'Foo', 578 state: { 579 routes: [ 580 { 581 name: 'Foe', 582 state: { 583 routes: [{ name: 'Baz', path }], 584 }, 585 }, 586 ], 587 }, 588 }, 589 ], 590 }; 591 592 testConversions(path, config, state); 593}); 594 595it('handles two initialRouteNames', () => { 596 const path = '/bar/sweet/apple/foe/bis/jane?answer=42&count=10&valid=true'; 597 const config = { 598 screens: { 599 Bar: { 600 path: 'bar/:type/:fruit', 601 screens: { 602 Foo: { 603 screens: { 604 Foe: { 605 path: 'foe', 606 screens: { 607 Baz: { 608 initialRouteName: 'Bos', 609 screens: { 610 Bos: { 611 path: 'bos', 612 exact: true, 613 }, 614 Bis: { 615 path: 'bis/:author', 616 stringify: { 617 author: (author: string) => author.replace(/^\w/, (c) => c.toLowerCase()), 618 }, 619 parse: { 620 author: (author: string) => author.replace(/^\w/, (c) => c.toUpperCase()), 621 count: Number, 622 valid: Boolean, 623 }, 624 }, 625 }, 626 }, 627 }, 628 }, 629 }, 630 }, 631 }, 632 }, 633 }, 634 }; 635 636 const state = { 637 routes: [ 638 { 639 name: 'Bar', 640 params: { author: 'Jane', fruit: 'apple', type: 'sweet' }, 641 state: { 642 routes: [ 643 { 644 name: 'Foo', 645 params: { author: 'Jane', fruit: 'apple', type: 'sweet' }, 646 state: { 647 routes: [ 648 { 649 name: 'Foe', 650 params: { author: 'Jane', fruit: 'apple', type: 'sweet' }, 651 state: { 652 routes: [ 653 { 654 name: 'Baz', 655 params: { 656 author: 'Jane', 657 fruit: 'apple', 658 type: 'sweet', 659 }, 660 state: { 661 index: 1, 662 routes: [ 663 { name: 'Bos' }, 664 { 665 name: 'Bis', 666 params: { 667 answer: '42', 668 author: 'Jane', 669 count: 10, 670 valid: true, 671 fruit: 'apple', 672 type: 'sweet', 673 }, 674 path, 675 }, 676 ], 677 }, 678 }, 679 ], 680 }, 681 }, 682 ], 683 }, 684 }, 685 ], 686 }, 687 }, 688 ], 689 }; 690 691 testConversions(path, config, state); 692}); 693 694it('accepts initialRouteName without config for it', () => { 695 const path = '/bar/sweet/apple/foe/bis/jane?answer=42&count=10&valid=true'; 696 const config = { 697 screens: { 698 Bar: { 699 path: 'bar/:type/:fruit', 700 screens: { 701 Foo: { 702 screens: { 703 Foe: { 704 path: 'foe', 705 screens: { 706 Baz: { 707 initialRouteName: 'Bas', 708 screens: { 709 Bos: { 710 path: 'bos', 711 exact: true, 712 }, 713 Bis: { 714 path: 'bis/:author', 715 stringify: { 716 author: (author: string) => author.replace(/^\w/, (c) => c.toLowerCase()), 717 }, 718 parse: { 719 author: (author: string) => author.replace(/^\w/, (c) => c.toUpperCase()), 720 count: Number, 721 valid: Boolean, 722 }, 723 }, 724 }, 725 }, 726 }, 727 }, 728 }, 729 }, 730 }, 731 }, 732 }, 733 }; 734 735 const state = { 736 routes: [ 737 { 738 name: 'Bar', 739 params: { author: 'Jane', fruit: 'apple', type: 'sweet' }, 740 state: { 741 routes: [ 742 { 743 name: 'Foo', 744 params: { author: 'Jane', fruit: 'apple', type: 'sweet' }, 745 state: { 746 routes: [ 747 { 748 name: 'Foe', 749 params: { author: 'Jane', fruit: 'apple', type: 'sweet' }, 750 state: { 751 routes: [ 752 { 753 name: 'Baz', 754 params: { 755 author: 'Jane', 756 fruit: 'apple', 757 type: 'sweet', 758 }, 759 state: { 760 index: 1, 761 routes: [ 762 { 763 name: 'Bas', 764 }, 765 { 766 name: 'Bis', 767 params: { 768 answer: '42', 769 author: 'Jane', 770 count: 10, 771 fruit: 'apple', 772 type: 'sweet', 773 valid: true, 774 }, 775 path: '/bar/sweet/apple/foe/bis/jane?answer=42&count=10&valid=true', 776 }, 777 ], 778 }, 779 }, 780 ], 781 }, 782 }, 783 ], 784 }, 785 }, 786 ], 787 }, 788 }, 789 ], 790 }; 791 792 testConversions(path, config, state); 793}); 794 795it('returns undefined if path is empty and no matching screen is present', () => { 796 const config = { 797 screens: { 798 Foo: { 799 screens: { 800 Foe: 'foe', 801 Bar: { 802 screens: { 803 Baz: 'baz', 804 }, 805 }, 806 }, 807 }, 808 }, 809 }; 810 811 const path = ''; 812 813 expect(getStateFromPath<object>(path, config)).toEqual(undefined); 814}); 815 816it('returns matching screen if path is empty', () => { 817 const path = ''; 818 const config = { 819 screens: { 820 Foo: { 821 screens: { 822 Foe: 'foe', 823 Bar: { 824 screens: { 825 Qux: '', 826 Baz: 'baz', 827 }, 828 }, 829 }, 830 }, 831 }, 832 }; 833 834 const state = { 835 routes: [ 836 { 837 name: 'Foo', 838 state: { 839 routes: [ 840 { 841 name: 'Bar', 842 state: { 843 routes: [{ name: 'Qux', path }], 844 }, 845 }, 846 ], 847 }, 848 }, 849 ], 850 }; 851 852 expect(getStateFromPath<object>(path, config)).toEqual(state); 853 expect(getStateFromPath<object>(getPathFromState<object>(state, config), config)).toEqual( 854 changePath(state, '/') 855 ); 856}); 857 858it('returns matching screen if path is only slash', () => { 859 const path = '/'; 860 const config = { 861 screens: { 862 Foo: { 863 screens: { 864 Foe: 'foe', 865 Bar: { 866 screens: { 867 Qux: '', 868 Baz: 'baz', 869 }, 870 }, 871 }, 872 }, 873 }, 874 }; 875 876 const state = { 877 routes: [ 878 { 879 name: 'Foo', 880 state: { 881 routes: [ 882 { 883 name: 'Bar', 884 state: { 885 routes: [{ name: 'Qux', path }], 886 }, 887 }, 888 ], 889 }, 890 }, 891 ], 892 }; 893 894 expect(getStateFromPath<object>(path, config)).toEqual(state); 895 expect(getStateFromPath<object>(getPathFromState<object>(state, config), config)).toEqual( 896 changePath(state, '/') 897 ); 898}); 899 900// Test `app/(app)/index.js` matching `/` 901it('returns matching screen if path is only slash and root is a group', () => { 902 expect( 903 getStateFromPath<object>('/', { 904 screens: { 905 '(app)/index': '(app)', 906 '[id]': ':id', 907 '[...404]': '*404', 908 }, 909 }) 910 ).toEqual({ routes: [{ name: '(app)/index', path: '/' }] }); 911}); 912 913// Test `app/(one)/(two)/index.js` matching `/` 914it('returns matching screen if path is only slash and root is a nested group', () => { 915 expect( 916 getStateFromPath<object>('/', { 917 screens: { 918 '(one)/(two)/index': '(one)/(two)', 919 '[id]': ':id', 920 '[...404]': '*404', 921 }, 922 }) 923 ).toEqual({ routes: [{ name: '(one)/(two)/index', path: '/' }] }); 924}); 925 926it('returns matching screen with params if path is empty', () => { 927 const path = '?foo=42'; 928 const config = { 929 screens: { 930 Foo: { 931 screens: { 932 Foe: 'foe', 933 Bar: { 934 screens: { 935 Qux: { 936 path: '', 937 parse: { foo: Number }, 938 }, 939 Baz: 'baz', 940 }, 941 }, 942 }, 943 }, 944 }, 945 }; 946 947 const state = { 948 routes: [ 949 { 950 name: 'Foo', 951 state: { 952 routes: [ 953 { 954 name: 'Bar', 955 state: { 956 routes: [{ name: 'Qux', params: { foo: 42 }, path }], 957 }, 958 }, 959 ], 960 }, 961 }, 962 ], 963 }; 964 965 expect(getStateFromPath<object>(path, config)).toEqual(state); 966 expect(getStateFromPath<object>(getPathFromState<object>(state, config), config)).toEqual( 967 changePath(state, '/?foo=42') 968 ); 969}); 970 971it("doesn't match nested screen if path is empty", () => { 972 const config = { 973 screens: { 974 Foo: { 975 screens: { 976 Foe: 'foe', 977 Bar: { 978 path: 'bar', 979 screens: { 980 Qux: { 981 path: '', 982 parse: { foo: Number }, 983 }, 984 }, 985 }, 986 }, 987 }, 988 }, 989 }; 990 991 const path = ''; 992 993 expect(getStateFromPath<object>(path, config)).toEqual(undefined); 994}); 995 996it('chooses more exhaustive pattern', () => { 997 const path = '/foo/5'; 998 999 const config = { 1000 screens: { 1001 Foe: { 1002 path: '/', 1003 initialRouteName: 'Foo', 1004 screens: { 1005 Foo: 'foo', 1006 Bis: { 1007 path: 'foo/:id', 1008 parse: { 1009 id: Number, 1010 }, 1011 }, 1012 }, 1013 }, 1014 }, 1015 }; 1016 1017 const state = { 1018 routes: [ 1019 { 1020 name: 'Foe', 1021 params: { 1022 id: 5, 1023 }, 1024 state: { 1025 index: 1, 1026 routes: [ 1027 { 1028 name: 'Foo', 1029 }, 1030 { 1031 name: 'Bis', 1032 params: { id: 5 }, 1033 path, 1034 }, 1035 ], 1036 }, 1037 }, 1038 ], 1039 }; 1040 1041 testConversions(path, config, state); 1042}); 1043 1044it('handles same paths beginnings', () => { 1045 const path = '/foos'; 1046 1047 const config = { 1048 screens: { 1049 Foe: { 1050 path: '/', 1051 initialRouteName: 'Foo', 1052 screens: { 1053 Foo: 'foo', 1054 Bis: { 1055 path: 'foos', 1056 }, 1057 }, 1058 }, 1059 }, 1060 }; 1061 1062 const state = { 1063 routes: [ 1064 { 1065 name: 'Foe', 1066 state: { 1067 index: 1, 1068 routes: [ 1069 { 1070 name: 'Foo', 1071 }, 1072 { 1073 name: 'Bis', 1074 path, 1075 }, 1076 ], 1077 }, 1078 }, 1079 ], 1080 }; 1081 1082 testConversions(path, config, state); 1083}); 1084 1085it('handles same paths beginnings with params', () => { 1086 const path = '/foos/5'; 1087 1088 const config = { 1089 screens: { 1090 Foe: { 1091 path: '/', 1092 initialRouteName: 'Foo', 1093 screens: { 1094 Foo: 'foo', 1095 Bis: { 1096 path: 'foos/:id', 1097 parse: { 1098 id: Number, 1099 }, 1100 }, 1101 }, 1102 }, 1103 }, 1104 }; 1105 1106 const state = { 1107 routes: [ 1108 { 1109 name: 'Foe', 1110 params: { 1111 id: 5, 1112 }, 1113 1114 state: { 1115 index: 1, 1116 routes: [ 1117 { 1118 name: 'Foo', 1119 }, 1120 { 1121 name: 'Bis', 1122 params: { id: 5 }, 1123 path, 1124 }, 1125 ], 1126 }, 1127 }, 1128 ], 1129 }; 1130 1131 testConversions(path, config, state); 1132}); 1133 1134it('handles not taking path with too many segments', () => { 1135 const path = '/foos/5'; 1136 1137 const config = { 1138 screens: { 1139 Foe: { 1140 path: '/', 1141 initialRouteName: 'Foo', 1142 screens: { 1143 Foo: 'foo', 1144 Bis: { 1145 path: 'foos/:id', 1146 parse: { 1147 id: Number, 1148 }, 1149 }, 1150 Bas: { 1151 path: 'foos/:id/:nip', 1152 parse: { 1153 id: Number, 1154 pwd: Number, 1155 }, 1156 }, 1157 }, 1158 }, 1159 }, 1160 }; 1161 1162 const state = { 1163 routes: [ 1164 { 1165 name: 'Foe', 1166 params: { 1167 id: 5, 1168 }, 1169 1170 state: { 1171 index: 1, 1172 routes: [ 1173 { 1174 name: 'Foo', 1175 }, 1176 { 1177 name: 'Bis', 1178 params: { id: 5 }, 1179 path, 1180 }, 1181 ], 1182 }, 1183 }, 1184 ], 1185 }; 1186 1187 testConversions(path, config, state); 1188}); 1189 1190it('handles differently ordered params v1', () => { 1191 const path = '/foos/5/res/20'; 1192 1193 const config = { 1194 screens: { 1195 Foe: { 1196 path: '/', 1197 initialRouteName: 'Foo', 1198 screens: { 1199 Foo: 'foo', 1200 Bis: { 1201 path: 'foos/:id', 1202 parse: { 1203 id: Number, 1204 }, 1205 }, 1206 Bas: { 1207 path: 'foos/:id/res/:pwd', 1208 parse: { 1209 id: Number, 1210 pwd: Number, 1211 }, 1212 }, 1213 }, 1214 }, 1215 }, 1216 }; 1217 1218 const state = { 1219 routes: [ 1220 { 1221 name: 'Foe', 1222 params: { 1223 id: 5, 1224 pwd: 20, 1225 }, 1226 state: { 1227 index: 1, 1228 routes: [ 1229 { 1230 name: 'Foo', 1231 }, 1232 { 1233 name: 'Bas', 1234 params: { id: 5, pwd: 20 }, 1235 path, 1236 }, 1237 ], 1238 }, 1239 }, 1240 ], 1241 }; 1242 1243 testConversions(path, config, state); 1244}); 1245 1246it('handles differently ordered params v2', () => { 1247 const path = '/5/20/foos/res'; 1248 1249 const config = { 1250 screens: { 1251 Foe: { 1252 path: '/', 1253 initialRouteName: 'Foo', 1254 screens: { 1255 Foo: 'foo', 1256 Bis: { 1257 path: 'foos/:id', 1258 parse: { 1259 id: Number, 1260 }, 1261 }, 1262 Bas: { 1263 path: ':id/:pwd/foos/res', 1264 parse: { 1265 id: Number, 1266 pwd: Number, 1267 }, 1268 }, 1269 }, 1270 }, 1271 }, 1272 }; 1273 1274 const state = { 1275 routes: [ 1276 { 1277 name: 'Foe', 1278 params: { 1279 id: 5, 1280 pwd: 20, 1281 }, 1282 1283 state: { 1284 index: 1, 1285 routes: [ 1286 { 1287 name: 'Foo', 1288 }, 1289 { 1290 name: 'Bas', 1291 params: { id: 5, pwd: 20 }, 1292 path, 1293 }, 1294 ], 1295 }, 1296 }, 1297 ], 1298 }; 1299 1300 testConversions(path, config, state); 1301}); 1302 1303it('handles differently ordered params v3', () => { 1304 const path = '/foos/5/20/res'; 1305 1306 const config = { 1307 screens: { 1308 Foe: { 1309 path: '/', 1310 initialRouteName: 'Foo', 1311 screens: { 1312 Foo: 'foo', 1313 Bis: { 1314 path: 'foos/:id', 1315 parse: { 1316 id: Number, 1317 }, 1318 }, 1319 Bas: { 1320 path: 'foos/:id/:pwd/res', 1321 parse: { 1322 id: Number, 1323 pwd: Number, 1324 }, 1325 }, 1326 }, 1327 }, 1328 }, 1329 }; 1330 1331 const state = { 1332 routes: [ 1333 { 1334 name: 'Foe', 1335 params: { 1336 id: 5, 1337 pwd: 20, 1338 }, 1339 state: { 1340 index: 1, 1341 routes: [ 1342 { 1343 name: 'Foo', 1344 }, 1345 { 1346 name: 'Bas', 1347 params: { id: 5, pwd: 20 }, 1348 path, 1349 }, 1350 ], 1351 }, 1352 }, 1353 ], 1354 }; 1355 1356 testConversions(path, config, state); 1357}); 1358 1359it('handles differently ordered params v4', () => { 1360 const path = '5/foos/res/20'; 1361 1362 const config = { 1363 screens: { 1364 Foe: { 1365 path: '/', 1366 initialRouteName: 'Foo', 1367 screens: { 1368 Foo: 'foo', 1369 Bis: { 1370 path: 'foos/:id', 1371 parse: { 1372 id: Number, 1373 }, 1374 }, 1375 Bas: { 1376 path: ':id/foos/res/:pwd', 1377 parse: { 1378 id: Number, 1379 pwd: Number, 1380 }, 1381 }, 1382 }, 1383 }, 1384 }, 1385 }; 1386 1387 const state = { 1388 routes: [ 1389 { 1390 name: 'Foe', 1391 params: { 1392 id: 5, 1393 pwd: 20, 1394 }, 1395 state: { 1396 index: 1, 1397 routes: [ 1398 { 1399 name: 'Foo', 1400 }, 1401 { 1402 name: 'Bas', 1403 params: { id: 5, pwd: 20 }, 1404 path, 1405 }, 1406 ], 1407 }, 1408 }, 1409 ], 1410 }; 1411 1412 expect(getStateFromPath<object>(path, config)).toEqual(state); 1413 expect(getStateFromPath<object>(getPathFromState<object>(state, config), config)).toEqual( 1414 changePath(state, '/5/foos/res/20') 1415 ); 1416}); 1417 1418it('handles simple optional params', () => { 1419 const path = '/foos/5'; 1420 1421 const config = { 1422 screens: { 1423 Foe: { 1424 path: '/', 1425 initialRouteName: 'Foo', 1426 screens: { 1427 Foo: 'foo', 1428 Bis: { 1429 path: 'foo/:id', 1430 parse: { 1431 id: Number, 1432 }, 1433 }, 1434 Bas: { 1435 path: 'foos/:id/:nip?', 1436 parse: { 1437 id: Number, 1438 nip: Number, 1439 }, 1440 }, 1441 }, 1442 }, 1443 }, 1444 }; 1445 1446 const state = { 1447 routes: [ 1448 { 1449 name: 'Foe', 1450 params: { 1451 id: 5, 1452 }, 1453 state: { 1454 index: 1, 1455 routes: [ 1456 { 1457 name: 'Foo', 1458 }, 1459 { 1460 name: 'Bas', 1461 params: { id: 5 }, 1462 path, 1463 }, 1464 ], 1465 }, 1466 }, 1467 ], 1468 }; 1469 1470 testConversions(path, config, state); 1471}); 1472 1473it('handle 2 optional params at the end v1', () => { 1474 const path = '/foos/5'; 1475 1476 const config = { 1477 screens: { 1478 Foe: { 1479 path: '/', 1480 initialRouteName: 'Foo', 1481 screens: { 1482 Foo: 'foo', 1483 Bis: { 1484 path: 'foo/:id', 1485 parse: { 1486 id: Number, 1487 }, 1488 }, 1489 Bas: { 1490 path: 'foos/:id/:nip?/:pwd?', 1491 parse: { 1492 id: Number, 1493 nip: Number, 1494 }, 1495 }, 1496 }, 1497 }, 1498 }, 1499 }; 1500 1501 const state = { 1502 routes: [ 1503 { 1504 name: 'Foe', 1505 params: { 1506 id: 5, 1507 }, 1508 state: { 1509 index: 1, 1510 routes: [ 1511 { 1512 name: 'Foo', 1513 }, 1514 { 1515 name: 'Bas', 1516 params: { id: 5 }, 1517 path, 1518 }, 1519 ], 1520 }, 1521 }, 1522 ], 1523 }; 1524 1525 testConversions(path, config, state); 1526}); 1527 1528it('handle 2 optional params at the end v2', () => { 1529 const path = '/foos/5/10'; 1530 1531 const config = { 1532 screens: { 1533 Foe: { 1534 path: '/', 1535 initialRouteName: 'Foo', 1536 screens: { 1537 Foo: 'foo', 1538 Bis: { 1539 path: 'foo/:id', 1540 parse: { 1541 id: Number, 1542 }, 1543 }, 1544 Bas: { 1545 path: 'foos/:id/:nip?/:pwd?', 1546 parse: { 1547 id: Number, 1548 nip: Number, 1549 }, 1550 }, 1551 }, 1552 }, 1553 }, 1554 }; 1555 1556 const state = { 1557 routes: [ 1558 { 1559 name: 'Foe', 1560 params: { 1561 id: 5, 1562 nip: 10, 1563 }, 1564 1565 state: { 1566 index: 1, 1567 routes: [ 1568 { 1569 name: 'Foo', 1570 }, 1571 { 1572 name: 'Bas', 1573 params: { id: 5, nip: 10 }, 1574 path, 1575 }, 1576 ], 1577 }, 1578 }, 1579 ], 1580 }; 1581 1582 testConversions(path, config, state); 1583}); 1584 1585it('handle 2 optional params at the end v3', () => { 1586 const path = '/foos/5/10/15'; 1587 1588 const config = { 1589 screens: { 1590 Foe: { 1591 path: '/', 1592 initialRouteName: 'Foo', 1593 screens: { 1594 Foo: 'foo', 1595 Bis: { 1596 path: 'foo/:id', 1597 parse: { 1598 id: Number, 1599 }, 1600 }, 1601 Bas: { 1602 path: 'foos/:id/:nip?/:pwd?', 1603 parse: { 1604 id: Number, 1605 nip: Number, 1606 pwd: Number, 1607 }, 1608 }, 1609 }, 1610 }, 1611 }, 1612 }; 1613 1614 const state = { 1615 routes: [ 1616 { 1617 name: 'Foe', 1618 params: { 1619 nip: 10, 1620 pwd: 15, 1621 id: 5, 1622 }, 1623 state: { 1624 index: 1, 1625 routes: [ 1626 { 1627 name: 'Foo', 1628 }, 1629 { 1630 name: 'Bas', 1631 params: { id: 5, nip: 10, pwd: 15 }, 1632 path, 1633 }, 1634 ], 1635 }, 1636 }, 1637 ], 1638 }; 1639 1640 testConversions(path, config, state); 1641}); 1642 1643it('handle optional params in the middle v1', () => { 1644 const path = '/foos/5/10'; 1645 1646 const config = { 1647 screens: { 1648 Foe: { 1649 path: '/', 1650 initialRouteName: 'Foo', 1651 screens: { 1652 Foo: 'foo', 1653 Bis: { 1654 path: 'foo/:id', 1655 parse: { 1656 id: Number, 1657 }, 1658 }, 1659 Bas: { 1660 path: 'foos/:id/:nip?/:pwd', 1661 parse: { 1662 id: Number, 1663 nip: Number, 1664 pwd: Number, 1665 }, 1666 }, 1667 }, 1668 }, 1669 }, 1670 }; 1671 1672 const state = { 1673 routes: [ 1674 { 1675 name: 'Foe', 1676 params: { 1677 id: 5, 1678 pwd: 10, 1679 }, 1680 1681 state: { 1682 index: 1, 1683 routes: [ 1684 { 1685 name: 'Foo', 1686 }, 1687 { 1688 name: 'Bas', 1689 params: { id: 5, pwd: 10 }, 1690 path, 1691 }, 1692 ], 1693 }, 1694 }, 1695 ], 1696 }; 1697 1698 testConversions(path, config, state); 1699}); 1700 1701it('handle optional params in the middle v2', () => { 1702 const path = '/foos/5/10/15'; 1703 1704 const config = { 1705 screens: { 1706 Foe: { 1707 path: '/', 1708 initialRouteName: 'Foo', 1709 screens: { 1710 Foo: 'foo', 1711 Bis: { 1712 path: 'foo/:id', 1713 parse: { 1714 id: Number, 1715 }, 1716 }, 1717 Bas: { 1718 path: 'foos/:id/:nip?/:pwd', 1719 parse: { 1720 id: Number, 1721 nip: Number, 1722 pwd: Number, 1723 }, 1724 }, 1725 }, 1726 }, 1727 }, 1728 }; 1729 1730 const state = { 1731 routes: [ 1732 { 1733 name: 'Foe', 1734 params: { 1735 id: 5, 1736 nip: 10, 1737 pwd: 15, 1738 }, 1739 state: { 1740 index: 1, 1741 routes: [ 1742 { 1743 name: 'Foo', 1744 }, 1745 { 1746 name: 'Bas', 1747 params: { id: 5, nip: 10, pwd: 15 }, 1748 path, 1749 }, 1750 ], 1751 }, 1752 }, 1753 ], 1754 }; 1755 1756 testConversions(path, config, state); 1757}); 1758 1759it('handle optional params in the middle v3', () => { 1760 const path = '/foos/5/10/15'; 1761 1762 const config = { 1763 screens: { 1764 Foe: { 1765 path: '/', 1766 initialRouteName: 'Foo', 1767 screens: { 1768 Foo: 'foo', 1769 Bis: { 1770 path: 'foo/:id', 1771 parse: { 1772 id: Number, 1773 }, 1774 }, 1775 Bas: { 1776 path: 'foos/:id/:nip?/:pwd/:smh', 1777 parse: { 1778 id: Number, 1779 nip: Number, 1780 pwd: Number, 1781 smh: Number, 1782 }, 1783 }, 1784 }, 1785 }, 1786 }, 1787 }; 1788 1789 const state = { 1790 routes: [ 1791 { 1792 name: 'Foe', 1793 params: { 1794 id: 5, 1795 pwd: 10, 1796 smh: 15, 1797 }, 1798 1799 state: { 1800 index: 1, 1801 routes: [ 1802 { 1803 name: 'Foo', 1804 }, 1805 { 1806 name: 'Bas', 1807 params: { id: 5, pwd: 10, smh: 15 }, 1808 path, 1809 }, 1810 ], 1811 }, 1812 }, 1813 ], 1814 }; 1815 1816 testConversions(path, config, state); 1817}); 1818 1819it('handle optional params in the middle v4', () => { 1820 const path = '/foos/5/10'; 1821 1822 const config = { 1823 screens: { 1824 Foe: { 1825 path: '/', 1826 initialRouteName: 'Foo', 1827 screens: { 1828 Foo: 'foo', 1829 Bis: { 1830 path: 'foo/:id', 1831 parse: { 1832 id: Number, 1833 }, 1834 }, 1835 Bas: { 1836 path: 'foos/:nip?/:pwd/:smh?/:id', 1837 parse: { 1838 id: Number, 1839 nip: Number, 1840 pwd: Number, 1841 smh: Number, 1842 }, 1843 }, 1844 }, 1845 }, 1846 }, 1847 }; 1848 1849 const state = { 1850 routes: [ 1851 { 1852 name: 'Foe', 1853 params: { 1854 id: 10, 1855 pwd: 5, 1856 }, 1857 state: { 1858 index: 1, 1859 routes: [ 1860 { 1861 name: 'Foo', 1862 }, 1863 { 1864 name: 'Bas', 1865 params: { pwd: 5, id: 10 }, 1866 path, 1867 }, 1868 ], 1869 }, 1870 }, 1871 ], 1872 }; 1873 1874 testConversions(path, config, state); 1875}); 1876 1877it('handle optional params in the middle v5', () => { 1878 const path = '/foos/5/10/15'; 1879 1880 const config = { 1881 screens: { 1882 Foe: { 1883 path: '/', 1884 initialRouteName: 'Foo', 1885 screens: { 1886 Foo: 'foo', 1887 Bis: { 1888 path: 'foo/:id', 1889 parse: { 1890 id: Number, 1891 }, 1892 }, 1893 Bas: { 1894 path: 'foos/:nip?/:pwd/:smh?/:id', 1895 parse: { 1896 id: Number, 1897 nip: Number, 1898 pwd: Number, 1899 smh: Number, 1900 }, 1901 }, 1902 }, 1903 }, 1904 }, 1905 }; 1906 1907 const state = { 1908 routes: [ 1909 { 1910 name: 'Foe', 1911 params: { 1912 id: 15, 1913 nip: 5, 1914 pwd: 10, 1915 }, 1916 1917 state: { 1918 index: 1, 1919 routes: [ 1920 { 1921 name: 'Foo', 1922 }, 1923 { 1924 name: 'Bas', 1925 params: { nip: 5, pwd: 10, id: 15 }, 1926 path, 1927 }, 1928 ], 1929 }, 1930 }, 1931 ], 1932 }; 1933 1934 testConversions(path, config, state); 1935}); 1936 1937it('handle optional params in the beginning v1', () => { 1938 const path = '5/10/foos/15'; 1939 1940 const config = { 1941 screens: { 1942 Foe: { 1943 path: '/', 1944 initialRouteName: 'Foo', 1945 screens: { 1946 Foo: 'foo', 1947 Bis: { 1948 path: 'foo/:id', 1949 parse: { 1950 id: Number, 1951 }, 1952 }, 1953 Bas: { 1954 path: ':nip?/:pwd/foos/:smh?/:id', 1955 parse: { 1956 id: Number, 1957 nip: Number, 1958 pwd: Number, 1959 smh: Number, 1960 }, 1961 }, 1962 }, 1963 }, 1964 }, 1965 }; 1966 1967 const state = { 1968 routes: [ 1969 { 1970 name: 'Foe', 1971 params: { 1972 id: 15, 1973 nip: 5, 1974 pwd: 10, 1975 }, 1976 state: { 1977 index: 1, 1978 routes: [ 1979 { 1980 name: 'Foo', 1981 }, 1982 { 1983 name: 'Bas', 1984 params: { nip: 5, pwd: 10, id: 15 }, 1985 path, 1986 }, 1987 ], 1988 }, 1989 }, 1990 ], 1991 }; 1992 1993 expect(getStateFromPath<object>(path, config)).toEqual(state); 1994 expect(getStateFromPath<object>(getPathFromState<object>(state, config), config)).toEqual( 1995 changePath(state, '/5/10/foos/15') 1996 ); 1997}); 1998 1999it('handle optional params in the beginning v2', () => { 2000 const path = '5/10/foos/15'; 2001 2002 const config = { 2003 screens: { 2004 Foe: { 2005 path: '/', 2006 initialRouteName: 'Foo', 2007 screens: { 2008 Foo: 'foo', 2009 Bis: { 2010 path: 'foo/:id', 2011 parse: { 2012 id: Number, 2013 }, 2014 }, 2015 Bas: { 2016 path: ':nip?/:smh?/:pwd/foos/:id', 2017 parse: { 2018 id: Number, 2019 nip: Number, 2020 pwd: Number, 2021 smh: Number, 2022 }, 2023 }, 2024 }, 2025 }, 2026 }, 2027 }; 2028 2029 const state = { 2030 routes: [ 2031 { 2032 name: 'Foe', 2033 params: { 2034 id: 15, 2035 nip: 5, 2036 pwd: 10, 2037 }, 2038 state: { 2039 index: 1, 2040 routes: [ 2041 { 2042 name: 'Foo', 2043 }, 2044 { 2045 name: 'Bas', 2046 params: { nip: 5, pwd: 10, id: 15 }, 2047 path, 2048 }, 2049 ], 2050 }, 2051 }, 2052 ], 2053 }; 2054 2055 expect(getStateFromPath<object>(path, config)).toEqual(state); 2056 expect(getStateFromPath<object>(getPathFromState<object>(state, config), config)).toEqual( 2057 changePath(state, '/5/10/foos/15') 2058 ); 2059}); 2060 2061it('merges parent patterns if needed', () => { 2062 const path = 'foo/42/baz/babel'; 2063 2064 const config = { 2065 screens: { 2066 Foo: { 2067 path: 'foo/:bar', 2068 parse: { 2069 bar: Number, 2070 }, 2071 screens: { 2072 Baz: 'baz/:qux', 2073 }, 2074 }, 2075 }, 2076 }; 2077 2078 const state = { 2079 routes: [ 2080 { 2081 name: 'Foo', 2082 params: { bar: 42, qux: 'babel' }, 2083 state: { 2084 routes: [ 2085 { 2086 name: 'Baz', 2087 params: { bar: 42, qux: 'babel' }, 2088 path, 2089 }, 2090 ], 2091 }, 2092 }, 2093 ], 2094 }; 2095 2096 expect(getStateFromPath<object>(path, config)).toEqual(state); 2097 expect(getStateFromPath<object>(getPathFromState<object>(state, config), config)).toEqual( 2098 changePath(state, '/foo/42/baz/babel') 2099 ); 2100}); 2101 2102it('ignores extra slashes in the pattern', () => { 2103 const path = '/bar/42'; 2104 const config = { 2105 screens: { 2106 Foo: { 2107 screens: { 2108 Bar: { 2109 path: '/bar//:id/', 2110 }, 2111 }, 2112 }, 2113 }, 2114 }; 2115 2116 const state = { 2117 routes: [ 2118 { 2119 name: 'Foo', 2120 params: { 2121 id: '42', 2122 }, 2123 state: { 2124 routes: [ 2125 { 2126 name: 'Bar', 2127 params: { id: '42' }, 2128 path, 2129 }, 2130 ], 2131 }, 2132 }, 2133 ], 2134 }; 2135 2136 testConversions(path, config, state); 2137}); 2138 2139it('matches wildcard patterns at root', () => { 2140 const path = '/test/bar/42/whatever'; 2141 const config = configFromFs(['[...404].js', 'foo/bar.js', 'index.js']); 2142 const state = { 2143 routes: [ 2144 { 2145 params: { 2146 '404': ['test', 'bar', '42', 'whatever'], 2147 }, 2148 name: '[...404]', 2149 path, 2150 }, 2151 ], 2152 }; 2153 2154 testConversions(path, config, state); 2155}); 2156 2157it('matches wildcard patterns at nested level', () => { 2158 const path = '/bar/42/whatever/baz/initt'; 2159 2160 const config = configFromFs([ 2161 '(foo)/_layout.tsx', 2162 '(foo)/bar/_layout.tsx', 2163 '(foo)/bar/[id].tsx', 2164 '(foo)/bar/[...rest].tsx', 2165 ]); 2166 2167 const state = { 2168 routes: [ 2169 { 2170 name: '(foo)', 2171 params: { rest: ['42', 'whatever', 'baz', 'initt'] }, 2172 state: { 2173 routes: [ 2174 { 2175 name: 'bar', 2176 params: { rest: ['42', 'whatever', 'baz', 'initt'] }, 2177 state: { 2178 routes: [ 2179 { 2180 name: '[...rest]', 2181 params: { 2182 rest: ['42', 'whatever', 'baz', 'initt'], 2183 }, 2184 path: '/bar/42/whatever/baz/initt', 2185 }, 2186 ], 2187 }, 2188 }, 2189 ], 2190 }, 2191 }, 2192 ], 2193 }; 2194 2195 testConversions(path, config, state); 2196}); 2197 2198xit('matches wildcard patterns at nested level with exact', () => { 2199 const path = '/whatever'; 2200 const config = { 2201 screens: { 2202 Foo: { 2203 screens: { 2204 Bar: { 2205 path: '/bar/:id/', 2206 screens: { 2207 404: { 2208 path: '*404', 2209 exact: true, 2210 }, 2211 }, 2212 }, 2213 Baz: {}, 2214 }, 2215 }, 2216 }, 2217 }; 2218 2219 const state = { 2220 routes: [ 2221 { 2222 name: 'Foo', 2223 params: { 2224 '404': ['whatever'], 2225 }, 2226 state: { 2227 routes: [ 2228 { 2229 name: 'Bar', 2230 params: { 2231 '404': ['whatever'], 2232 }, 2233 state: { 2234 routes: [ 2235 { 2236 name: '404', 2237 params: { 2238 '404': ['whatever'], 2239 }, 2240 path, 2241 }, 2242 ], 2243 }, 2244 }, 2245 ], 2246 }, 2247 }, 2248 ], 2249 }; 2250 2251 expect(getStateFromPath<object>(path, config)).toEqual(state); 2252 expect(getStateFromPath<object>(getPathFromState<object>(state, config), config)).toEqual( 2253 changePath(state, path) 2254 ); 2255}); 2256 2257it('tries to match wildcard patterns at the end', () => { 2258 const path = '/bar/42/test'; 2259 const config = { 2260 screens: { 2261 bar: { 2262 path: 'bar', 2263 screens: { 2264 '[...404]': '*404', 2265 '[userSlug]': ':userSlug', 2266 ':id': { 2267 path: ':id', 2268 screens: { 2269 test: 'test', 2270 }, 2271 }, 2272 }, 2273 }, 2274 }, 2275 }; 2276 2277 const state = { 2278 routes: [ 2279 { 2280 name: 'bar', 2281 params: { id: '42' }, 2282 state: { 2283 routes: [ 2284 { 2285 name: ':id', 2286 params: { id: '42' }, 2287 state: { 2288 routes: [ 2289 { 2290 name: 'test', 2291 params: { id: '42' }, 2292 path: '/bar/42/test', 2293 }, 2294 ], 2295 }, 2296 }, 2297 ], 2298 }, 2299 }, 2300 ], 2301 }; 2302 2303 testConversions(path, config, state); 2304}); 2305 2306it('uses nearest parent wildcard match for unmatched paths', () => { 2307 const path = '/bar/42/baz/test'; 2308 const config = { 2309 screens: { 2310 Foo: { 2311 screens: { 2312 Bar: { 2313 path: '/bar/:id/', 2314 screens: { 2315 Baz: 'baz', 2316 }, 2317 }, 2318 '[...404]': '*404', 2319 }, 2320 }, 2321 }, 2322 }; 2323 2324 const state = { 2325 routes: [ 2326 { 2327 name: 'Foo', 2328 params: { 2329 '404': ['bar', '42', 'baz', 'test'], 2330 }, 2331 state: { 2332 routes: [ 2333 { 2334 name: '[...404]', 2335 params: { 2336 '404': ['bar', '42', 'baz', 'test'], 2337 }, 2338 path, 2339 }, 2340 ], 2341 }, 2342 }, 2343 ], 2344 }; 2345 2346 testConversions(path, config, state); 2347}); 2348 2349it('throws if two screens map to the same pattern', () => { 2350 const path = '/bar/42/baz/test'; 2351 2352 expect(() => 2353 getStateFromPath<object>(path, { 2354 screens: { 2355 Foo: { 2356 screens: { 2357 Bar: { 2358 path: '/bar/:id/', 2359 screens: { 2360 Baz: 'baz', 2361 }, 2362 }, 2363 Bax: '/bar/:id/baz', 2364 }, 2365 }, 2366 }, 2367 }) 2368 ).toThrow( 2369 "The route pattern 'bar/:id/baz' resolves to both 'Foo//bar/:id/baz' and 'Foo/Bar/baz'. Patterns must be unique and cannot resolve to more than one route." 2370 ); 2371 2372 expect(() => 2373 getStateFromPath<object>(path, { 2374 screens: { 2375 Foo: { 2376 screens: { 2377 Bar: { 2378 path: '/bar/:id/', 2379 screens: { 2380 Baz: '', 2381 }, 2382 }, 2383 }, 2384 }, 2385 }, 2386 }) 2387 ).not.toThrow(); 2388}); 2389 2390it('correctly applies initialRouteName for config with similar route names', () => { 2391 const path = '/weekly-earnings'; 2392 2393 const config = { 2394 screens: { 2395 RootTabs: { 2396 screens: { 2397 HomeTab: { 2398 screens: { 2399 Home: '', 2400 WeeklyEarnings: 'weekly-earnings', 2401 EventDetails: 'event-details/:eventId', 2402 }, 2403 }, 2404 EarningsTab: { 2405 initialRouteName: 'Earnings', 2406 path: 'earnings', 2407 screens: { 2408 Earnings: '', 2409 WeeklyEarnings: 'weekly-earnings', 2410 }, 2411 }, 2412 }, 2413 }, 2414 }, 2415 }; 2416 2417 const state = { 2418 routes: [ 2419 { 2420 name: 'RootTabs', 2421 state: { 2422 routes: [ 2423 { 2424 name: 'HomeTab', 2425 state: { 2426 routes: [ 2427 { 2428 name: 'WeeklyEarnings', 2429 path, 2430 }, 2431 ], 2432 }, 2433 }, 2434 ], 2435 }, 2436 }, 2437 ], 2438 }; 2439 2440 testConversions(path, config, state); 2441}); 2442 2443it('correctly applies initialRouteName for config with similar route names v2', () => { 2444 const path = '/earnings/weekly-earnings'; 2445 2446 const config = { 2447 screens: { 2448 RootTabs: { 2449 screens: { 2450 HomeTab: { 2451 initialRouteName: 'Home', 2452 screens: { 2453 Home: '', 2454 WeeklyEarnings: 'weekly-earnings', 2455 }, 2456 }, 2457 EarningsTab: { 2458 initialRouteName: 'Earnings', 2459 path: 'earnings', 2460 screens: { 2461 Earnings: '', 2462 WeeklyEarnings: 'weekly-earnings', 2463 }, 2464 }, 2465 }, 2466 }, 2467 }, 2468 }; 2469 2470 const state = { 2471 routes: [ 2472 { 2473 name: 'RootTabs', 2474 state: { 2475 routes: [ 2476 { 2477 name: 'EarningsTab', 2478 state: { 2479 index: 1, 2480 routes: [ 2481 { 2482 name: 'Earnings', 2483 }, 2484 { 2485 name: 'WeeklyEarnings', 2486 path, 2487 }, 2488 ], 2489 }, 2490 }, 2491 ], 2492 }, 2493 }, 2494 ], 2495 }; 2496 2497 testConversions(path, config, state); 2498}); 2499 2500it('throws when invalid properties are specified in the config', () => { 2501 expect(() => 2502 getStateFromPath<object>('', { 2503 Foo: 'foo', 2504 Bar: { 2505 path: 'bar', 2506 }, 2507 } as any) 2508 ).toThrowErrorMatchingInlineSnapshot(` 2509 "Found invalid properties in the configuration: 2510 - Foo 2511 - Bar 2512 2513 Did you forget to specify them under a 'screens' property? 2514 2515 You can only specify the following properties: 2516 - initialRouteName 2517 - screens 2518 - _route 2519 2520 See https://reactnavigation.org/docs/configuring-links for more details on how to specify a linking configuration." 2521 `); 2522 2523 expect(() => 2524 getStateFromPath<object>('', { 2525 screens: { 2526 Foo: 'foo', 2527 Bar: { 2528 path: 'bar', 2529 }, 2530 Baz: { 2531 Qux: { 2532 path: 'qux', 2533 }, 2534 }, 2535 }, 2536 } as any) 2537 ).toThrowErrorMatchingInlineSnapshot(` 2538 "Found invalid properties in the configuration: 2539 - Qux 2540 2541 Did you forget to specify them under a 'screens' property? 2542 2543 You can only specify the following properties: 2544 - initialRouteName 2545 - screens 2546 - _route 2547 - path 2548 - exact 2549 - stringify 2550 - parse 2551 2552 See https://reactnavigation.org/docs/configuring-links for more details on how to specify a linking configuration." 2553 `); 2554}); 2555 2556function testConversions(path: string, config: any, state: any) { 2557 expect(getStateFromPath<object>(path, config)).toEqual(state); 2558 expect(getStateFromPath<object>(getPathFromState<object>(state, config), config)).toEqual(state); 2559} 2560