|
1 | | -import { describe, expect, it } from 'vitest' |
| 1 | +import { describe, expect, it, vi } from 'vitest' |
2 | 2 | import { findRouteMatch, processRouteTree } from '../src/new-process-route-tree' |
3 | 3 |
|
4 | 4 | describe('skipRouteOnParseError', () => { |
@@ -180,6 +180,46 @@ describe('skipRouteOnParseError', () => { |
180 | 180 | const result = findRouteMatch('/settings', processedTree) |
181 | 181 | expect(result?.route.id).toBe('/settings') |
182 | 182 | }) |
| 183 | + |
| 184 | + it('deep validated route can still fallback to sibling', () => { |
| 185 | + const tree = { |
| 186 | + id: '__root__', |
| 187 | + isRoot: true, |
| 188 | + fullPath: '/', |
| 189 | + children: [ |
| 190 | + { |
| 191 | + id: '/$a/$b/$c', |
| 192 | + fullPath: '/$a/$b/$c', |
| 193 | + path: '$a/$b/$c', |
| 194 | + options: { |
| 195 | + params: { |
| 196 | + parse: (params: Record<string, string>) => { |
| 197 | + // if (params.a !== 'one') throw new Error('Invalid a') |
| 198 | + // if (params.b !== 'two') throw new Error('Invalid b') |
| 199 | + if (params.c !== 'three') throw new Error('Invalid c') |
| 200 | + return params |
| 201 | + }, |
| 202 | + }, |
| 203 | + skipRouteOnParseError: { params: true }, |
| 204 | + }, |
| 205 | + }, |
| 206 | + { |
| 207 | + id: '/$x/$y/$z', |
| 208 | + fullPath: '/$x/$y/$z', |
| 209 | + path: '$x/$y/$z', |
| 210 | + }, |
| 211 | + ], |
| 212 | + } |
| 213 | + const { processedTree } = processRouteTree(tree) |
| 214 | + { |
| 215 | + const result = findRouteMatch('/one/two/three', processedTree) |
| 216 | + expect(result?.route.id).toBe('/$a/$b/$c') |
| 217 | + } |
| 218 | + { |
| 219 | + const result = findRouteMatch('/one/two/wrong', processedTree) |
| 220 | + expect(result?.route.id).toBe('/$x/$y/$z') |
| 221 | + } |
| 222 | + }) |
183 | 223 | }) |
184 | 224 |
|
185 | 225 | describe('regex-like validation patterns', () => { |
@@ -444,6 +484,10 @@ describe('skipRouteOnParseError', () => { |
444 | 484 | const helloResult = findRouteMatch('/abc/hello', processedTree) |
445 | 485 | expect(helloResult?.route.id).toBe('/$foo/hello') |
446 | 486 | expect(helloResult?.rawParams).toEqual({ foo: 'abc' }) |
| 487 | + |
| 488 | + // non-numeric foo should NOT match the children of the validated layout |
| 489 | + const barResult = findRouteMatch('/abc/bar', processedTree) |
| 490 | + expect(barResult).toBeNull() |
447 | 491 | }) |
448 | 492 | }) |
449 | 493 |
|
@@ -496,7 +540,7 @@ describe('skipRouteOnParseError', () => { |
496 | 540 |
|
497 | 541 | // invalid language should NOT match the validated optional route |
498 | 542 | // and since there's no dynamic fallback, it should return null |
499 | | - const invalidResult = findRouteMatch('/about/home', processedTree) |
| 543 | + const invalidResult = findRouteMatch('/it/home', processedTree) |
500 | 544 | expect(invalidResult).toBeNull() |
501 | 545 | }) |
502 | 546 |
|
@@ -660,84 +704,123 @@ describe('skipRouteOnParseError', () => { |
660 | 704 | const slugResult = findRouteMatch('/hello-world', processedTree) |
661 | 705 | expect(slugResult?.route.id).toBe('/$slug') |
662 | 706 | }) |
663 | | - }) |
664 | | - |
665 | | - describe('params.parse without skipRouteOnParseError', () => { |
666 | | - it('params.parse is NOT called during matching when skipRouteOnParseError is false', () => { |
667 | | - let parseCalled = false |
668 | | - const tree = { |
| 707 | + it('priority option can be used to influence order', () => { |
| 708 | + const alphabetical = { |
669 | 709 | id: '__root__', |
670 | 710 | isRoot: true, |
671 | 711 | fullPath: '/', |
672 | 712 | path: '/', |
673 | 713 | children: [ |
674 | 714 | { |
675 | | - id: '/$id', |
676 | | - fullPath: '/$id', |
677 | | - path: '$id', |
| 715 | + id: '/$a', |
| 716 | + fullPath: '/$a', |
| 717 | + path: '$a', |
678 | 718 | options: { |
679 | 719 | params: { |
680 | | - parse: (params: Record<string, string>) => { |
681 | | - parseCalled = true |
682 | | - return { id: parseInt(params.id!, 10) } |
683 | | - }, |
| 720 | + parse: (params: Record<string, string>) => params, |
| 721 | + }, |
| 722 | + skipRouteOnParseError: { |
| 723 | + params: true, |
| 724 | + priority: 1, // higher priority than /$z |
| 725 | + }, |
| 726 | + }, |
| 727 | + }, |
| 728 | + { |
| 729 | + id: '/$z', |
| 730 | + fullPath: '/$z', |
| 731 | + path: '$z', |
| 732 | + options: { |
| 733 | + params: { |
| 734 | + parse: (params: Record<string, string>) => params, |
| 735 | + }, |
| 736 | + skipRouteOnParseError: { |
| 737 | + params: true, |
| 738 | + priority: -1, // lower priority than /$a |
684 | 739 | }, |
685 | | - // skipRouteOnParseError is NOT set |
686 | 740 | }, |
687 | 741 | }, |
688 | 742 | ], |
689 | 743 | } |
690 | | - const { processedTree } = processRouteTree(tree) |
691 | | - const result = findRouteMatch('/123', processedTree) |
692 | | - expect(result?.route.id).toBe('/$id') |
693 | | - // parse should NOT be called during matching |
694 | | - expect(parseCalled).toBe(false) |
695 | | - // params should be raw strings |
696 | | - expect(result?.rawParams).toEqual({ id: '123' }) |
697 | | - }) |
698 | | - }) |
699 | | - |
700 | | - describe('edge cases', () => { |
701 | | - it('empty param value still goes through validation', () => { |
702 | | - const tree = { |
| 744 | + { |
| 745 | + const { processedTree } = processRouteTree(alphabetical) |
| 746 | + const result = findRouteMatch('/123', processedTree) |
| 747 | + expect(result?.route.id).toBe('/$a') |
| 748 | + } |
| 749 | + const reverse = { |
703 | 750 | id: '__root__', |
704 | 751 | isRoot: true, |
705 | 752 | fullPath: '/', |
706 | 753 | path: '/', |
707 | 754 | children: [ |
708 | 755 | { |
709 | | - id: '/prefix{$id}suffix', |
710 | | - fullPath: '/prefix{$id}suffix', |
711 | | - path: 'prefix{$id}suffix', |
| 756 | + id: '/$a', |
| 757 | + fullPath: '/$a', |
| 758 | + path: '$a', |
712 | 759 | options: { |
713 | 760 | params: { |
714 | | - parse: (params: Record<string, string>) => { |
715 | | - if (params.id === '') throw new Error('Empty not allowed') |
716 | | - return params |
717 | | - }, |
| 761 | + parse: (params: Record<string, string>) => params, |
| 762 | + }, |
| 763 | + skipRouteOnParseError: { |
| 764 | + params: true, |
| 765 | + priority: -1, // lower priority than /$z |
718 | 766 | }, |
719 | | - skipRouteOnParseError: { params: true }, |
720 | 767 | }, |
721 | 768 | }, |
722 | 769 | { |
723 | | - id: '/prefixsuffix', |
724 | | - fullPath: '/prefixsuffix', |
725 | | - path: 'prefixsuffix', |
726 | | - options: {}, |
| 770 | + id: '/$z', |
| 771 | + fullPath: '/$z', |
| 772 | + path: '$z', |
| 773 | + options: { |
| 774 | + params: { |
| 775 | + parse: (params: Record<string, string>) => params, |
| 776 | + }, |
| 777 | + skipRouteOnParseError: { |
| 778 | + params: true, |
| 779 | + priority: 1, // higher priority than /$a |
| 780 | + }, |
| 781 | + }, |
727 | 782 | }, |
728 | 783 | ], |
729 | 784 | } |
730 | | - const { processedTree } = processRouteTree(tree) |
731 | | - |
732 | | - // with value should match validated route |
733 | | - const withValue = findRouteMatch('/prefixFOOsuffix', processedTree) |
734 | | - expect(withValue?.route.id).toBe('/prefix{$id}suffix') |
| 785 | + { |
| 786 | + const { processedTree } = processRouteTree(reverse) |
| 787 | + const result = findRouteMatch('/123', processedTree) |
| 788 | + expect(result?.route.id).toBe('/$z') |
| 789 | + } |
| 790 | + }) |
| 791 | + }) |
735 | 792 |
|
736 | | - // empty value should fall through to static route |
737 | | - const empty = findRouteMatch('/prefixsuffix', processedTree) |
738 | | - expect(empty?.route.id).toBe('/prefixsuffix') |
| 793 | + describe('params.parse without skipRouteOnParseError', () => { |
| 794 | + it('params.parse is NOT called during matching when skipRouteOnParseError is false', () => { |
| 795 | + const parse = vi.fn() |
| 796 | + const tree = { |
| 797 | + id: '__root__', |
| 798 | + isRoot: true, |
| 799 | + fullPath: '/', |
| 800 | + path: '/', |
| 801 | + children: [ |
| 802 | + { |
| 803 | + id: '/$id', |
| 804 | + fullPath: '/$id', |
| 805 | + path: '$id', |
| 806 | + options: { |
| 807 | + params: { parse }, |
| 808 | + // skipRouteOnParseError is NOT set |
| 809 | + }, |
| 810 | + }, |
| 811 | + ], |
| 812 | + } |
| 813 | + const { processedTree } = processRouteTree(tree) |
| 814 | + const result = findRouteMatch('/123', processedTree) |
| 815 | + expect(result?.route.id).toBe('/$id') |
| 816 | + // parse should NOT be called during matching |
| 817 | + expect(parse).not.toHaveBeenCalled() |
| 818 | + // params should be raw strings |
| 819 | + expect(result?.rawParams).toEqual({ id: '123' }) |
739 | 820 | }) |
| 821 | + }) |
740 | 822 |
|
| 823 | + describe('edge cases', () => { |
741 | 824 | it('validation error type does not matter', () => { |
742 | 825 | const tree = { |
743 | 826 | id: '__root__', |
|
0 commit comments