/
githubmirror
/
angular
Обзор
Документация
Войти
/
githubmirror
/
angular
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/router/test/url_tree.spec.ts
376 строк
16 KB
rootvector2
fix(router): use safe hasOwnProperty when parsing query params
07 июл 2026, 20:09
07 июл 2026, 20:09
e5c37f2
Код
Авторство
О чём код?
/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ import {TestBed} from '@angular/core/testing'; import {Router} from '../src/router'; import { containsTree, DefaultUrlSerializer, exactMatchOptions, isActive, subsetMatchOptions, } from '../src/url_tree'; describe('UrlTree', () => { const serializer = new DefaultUrlSerializer(); describe('DefaultUrlSerializer', () => { let serializer: DefaultUrlSerializer; beforeEach(() => { serializer = new DefaultUrlSerializer(); }); it('should parse query parameters', () => { const tree = serializer.parse('/path/to?k=v&k/(a;b)=c'); expect(tree.queryParams).toEqual({ 'k': 'v', 'k/(a;b)': 'c', }); }); it('should allow question marks in query param values', () => { const tree = serializer.parse('/path/to?first=http://foo/bar?baz=true&second=123'); expect(tree.queryParams).toEqual({'first': 'http://foo/bar?baz=true', 'second': '123'}); }); it('should parse a query param named like an Object.prototype member', () => { const tree = serializer.parse('/path/to?hasOwnProperty=foo&bar=baz'); expect(tree.queryParams).toEqual({'hasOwnProperty': 'foo', 'bar': 'baz'}); }); it('create, serialize, parse, serialize results in same serialized tree with outlet and no primary children', () => { const router = TestBed.inject(Router); const th = router.createUrlTree(['/', {outlets: {a: ['a'], b: [{outlets: {a: ['b1']}}]}}]); const serialized = router.serializeUrl(th); const p = router.parseUrl(serialized); expect(router.serializeUrl(p)).toBe(serialized); }); it('should work with named outlet with primary and immediate named siblings', () => { const router = TestBed.inject(Router); const tree = router.createUrlTree([ { outlets: { primary: ['Home'], app: ['Welcome'], dock: [{outlets: {primary: 'left', 1: ['One', {pinned: true}]}}], }, }, ]); const url = tree.toString(); expect(url).toBe('/Home(app:Welcome//dock:/(left//1:One;pinned=true))'); const tree2 = serializer.parse(url); expect(serializer.serialize(tree2)).toBe(url); }); }); describe('containsTree', () => { describe('exact = true', () => { it('should return true when two tree are the same', () => { const url = '/one/(one//left:three)(right:four)'; const t1 = serializer.parse(url); const t2 = serializer.parse(url); expect(containsTree(t1, t2, exactMatchOptions)).toBe(true); expect(containsTree(t2, t1, exactMatchOptions)).toBe(true); }); it('should return true when queryParams are the same', () => { const t1 = serializer.parse('/one/two?test=1&page=5'); const t2 = serializer.parse('/one/two?test=1&page=5'); expect(containsTree(t1, t2, exactMatchOptions)).toBe(true); }); it('should return true when queryParams are the same but with different order', () => { const t1 = serializer.parse('/one/two?test=1&page=5'); const t2 = serializer.parse('/one/two?page=5&test=1'); expect(containsTree(t1, t2, exactMatchOptions)).toBe(true); }); it('should return true when queryParams contains array params that are the same', () => { const t1 = serializer.parse('/one/two?test=a&test=b&pages=5&pages=6'); const t2 = serializer.parse('/one/two?test=a&test=b&pages=5&pages=6'); expect(containsTree(t1, t2, exactMatchOptions)).toBe(true); }); it('should return false when queryParams contains array params but are not the same', () => { const t1 = serializer.parse('/one/two?test=a&test=b&pages=5&pages=6'); const t2 = serializer.parse('/one/two?test=a&test=b&pages=5&pages=7'); expect(containsTree(t1, t2, subsetMatchOptions)).toBe(false); }); it('should return false when queryParams are not the same', () => { const t1 = serializer.parse('/one/two?test=1&page=5'); const t2 = serializer.parse('/one/two?test=1'); expect(containsTree(t1, t2, exactMatchOptions)).toBe(false); }); it('should return false when queryParams are not the same (multiple params)', () => { const t1 = serializer.parse('/one/two?test=4&test=4&test=2'); const t2 = serializer.parse('/one/two?test=4&test=3&test=2'); expect(containsTree(t1, t2, subsetMatchOptions)).toBe(false); }); it('should return true when queryParams are the same in different order', () => { const t1 = serializer.parse('/one/two?test=4&test=3&test=2'); const t2 = serializer.parse('/one/two?test=2&test=3&test=4'); expect(containsTree(t1, t2, subsetMatchOptions)).toBe(true); }); it('should return true when queryParams are the same in different order (with duplicates)', () => { const t1 = serializer.parse('/one/two?test=4&test=4&test=1'); const t2 = serializer.parse('/one/two?test=1&test=4&test=4'); expect(containsTree(t1, t2, subsetMatchOptions)).toBe(true); }); it('should return false when containee is missing queryParams', () => { const t1 = serializer.parse('/one/two?page=5'); const t2 = serializer.parse('/one/two'); expect(containsTree(t1, t2, exactMatchOptions)).toBe(false); }); it('should return false when paths are not the same', () => { const t1 = serializer.parse('/one/two(right:three)'); const t2 = serializer.parse('/one/two2(right:three)'); expect(containsTree(t1, t2, exactMatchOptions)).toBe(false); }); it('should return false when container has an extra child', () => { const t1 = serializer.parse('/one/two(right:three)'); const t2 = serializer.parse('/one/two'); expect(containsTree(t1, t2, exactMatchOptions)).toBe(false); }); it('should return false when containee has an extra child', () => { const t1 = serializer.parse('/one/two'); const t2 = serializer.parse('/one/two(right:three)'); expect(containsTree(t1, t2, exactMatchOptions)).toBe(false); }); }); describe('isActive', () => { it('should allow partial match options and use subset match options as default', () => { const router = { parseUrl: (url: string) => serializer.parse(url), lastSuccessfulNavigation: () => ({finalUrl: serializer.parse('/one/two?a=1&b=2')}), } as unknown as Router; const t2 = serializer.parse('/one/two?a=1'); // With partial options: paths: 'exact'. // derived options should be: // paths: 'exact' // queryParams: 'subset' (default) // matrixParams: 'ignored' (default) // fragment: 'ignored' (default) // This should return true because paths match exactly, and queryParams is subset (t2 is subset of t1) expect(isActive(t2, router, {paths: 'exact'})()).toBe(true); }); it('should use subset match options as base for other properties', () => { const router = { parseUrl: (url: string) => serializer.parse(url), lastSuccessfulNavigation: () => ({finalUrl: serializer.parse('/one/two#frag')}), } as unknown as Router; const t2 = serializer.parse('/one/two#diff'); // fragment is ignored by default in subsetMatchOptions expect(isActive(t2, router, {paths: 'exact'})()).toBe(true); }); }); describe('exact = false', () => { it('should return true when containee is missing a segment', () => { const t1 = serializer.parse('/one/(two//left:three)(right:four)'); const t2 = serializer.parse('/one/(two//left:three)'); expect(containsTree(t1, t2, subsetMatchOptions)).toBe(true); }); it('should return true when containee is missing some paths', () => { const t1 = serializer.parse('/one/two/three'); const t2 = serializer.parse('/one/two'); expect(containsTree(t1, t2, subsetMatchOptions)).toBe(true); }); it('should return true container has its paths split into multiple segments', () => { const t1 = serializer.parse('/one/(two//left:three)'); const t2 = serializer.parse('/one/two'); expect(containsTree(t1, t2, subsetMatchOptions)).toBe(true); }); it('should return false when containee has extra segments', () => { const t1 = serializer.parse('/one/two'); const t2 = serializer.parse('/one/(two//left:three)'); expect(containsTree(t1, t2, subsetMatchOptions)).toBe(false); }); it('should return false containee has segments that the container does not have', () => { const t1 = serializer.parse('/one/(two//left:three)'); const t2 = serializer.parse('/one/(two//right:four)'); expect(containsTree(t1, t2, subsetMatchOptions)).toBe(false); }); it('should return false when containee has extra paths', () => { const t1 = serializer.parse('/one'); const t2 = serializer.parse('/one/two'); expect(containsTree(t1, t2, subsetMatchOptions)).toBe(false); }); it('should return true when queryParams are the same', () => { const t1 = serializer.parse('/one/two?test=1&page=5'); const t2 = serializer.parse('/one/two?test=1&page=5'); expect(containsTree(t1, t2, subsetMatchOptions)).toBe(true); }); it('should return true when container contains containees queryParams', () => { const t1 = serializer.parse('/one/two?test=1&u=5'); const t2 = serializer.parse('/one/two?u=5'); expect(containsTree(t1, t2, subsetMatchOptions)).toBe(true); }); it('should return true when containee does not have queryParams', () => { const t1 = serializer.parse('/one/two?page=5'); const t2 = serializer.parse('/one/two'); expect(containsTree(t1, t2, subsetMatchOptions)).toBe(true); }); it('should return false when containee has but container does not have queryParams', () => { const t1 = serializer.parse('/one/two'); const t2 = serializer.parse('/one/two?page=1'); expect(containsTree(t1, t2, subsetMatchOptions)).toBe(false); }); it('should return true when container has array params but containee does not have', () => { const t1 = serializer.parse('/one/two?test=a&test=b&pages=5&pages=6'); const t2 = serializer.parse('/one/two?test=a&test=b'); expect(containsTree(t1, t2, subsetMatchOptions)).toBe(true); }); it('should return false when containee has array params but container does not have', () => { const t1 = serializer.parse('/one/two?test=a&test=b'); const t2 = serializer.parse('/one/two?test=a&test=b&pages=5&pages=6'); expect(containsTree(t1, t2, subsetMatchOptions)).toBe(false); }); it('should return false when containee has different queryParams', () => { const t1 = serializer.parse('/one/two?page=5'); const t2 = serializer.parse('/one/two?test=1'); expect(containsTree(t1, t2, subsetMatchOptions)).toBe(false); }); it('should return false when containee has more queryParams than container', () => { const t1 = serializer.parse('/one/two?page=5'); const t2 = serializer.parse('/one/two?page=5&test=1'); expect(containsTree(t1, t2, subsetMatchOptions)).toBe(false); }); }); describe('ignored query params', () => { it('should return true when queryParams differ but are ignored', () => { const t1 = serializer.parse('/?test=1&page=2'); const t2 = serializer.parse('/?test=3&page=4&x=y'); expect(containsTree(t1, t2, {...exactMatchOptions, queryParams: 'ignored'})).toBe(true); }); }); describe('fragment', () => { it('should return false when fragments differ but options require exact match', () => { const t1 = serializer.parse('/#fragment1'); const t2 = serializer.parse('/#fragment2'); expect(containsTree(t1, t2, {...exactMatchOptions, fragment: 'exact'})).toBe(false); }); it('should return true when fragments differ but options ignore the fragment', () => { const t1 = serializer.parse('/#fragment1'); const t2 = serializer.parse('/#fragment2'); expect(containsTree(t1, t2, {...exactMatchOptions, fragment: 'ignored'})).toBe(true); }); }); describe('matrix params', () => { describe('ignored', () => { it('returns true when matrix params differ but are ignored', () => { const t1 = serializer.parse('/a;id=15;foo=foo'); const t2 = serializer.parse('/a;abc=123'); expect(containsTree(t1, t2, {...exactMatchOptions, matrixParams: 'ignored'})).toBe(true); }); }); describe('exact match', () => { const matrixParams = 'exact'; it('returns true when matrix params match', () => { const t1 = serializer.parse('/a;id=15;foo=foo'); const t2 = serializer.parse('/a;id=15;foo=foo'); expect(containsTree(t1, t2, {...exactMatchOptions, matrixParams})).toBe(true); }); it('returns false when matrix params differ', () => { const t1 = serializer.parse('/a;id=15;foo=foo'); const t2 = serializer.parse('/a;abc=123'); expect(containsTree(t1, t2, {...exactMatchOptions, matrixParams})).toBe(false); }); it('returns true when matrix params match on the subset of the matched url tree', () => { const t1 = serializer.parse('/a;id=15;foo=bar/c'); const t2 = serializer.parse('/a;id=15;foo=bar'); expect(containsTree(t1, t2, {...subsetMatchOptions, matrixParams})).toBe(true); }); it( 'should return true when matrix params match on subset of urlTree match ' + 'with container paths split into multiple segments', () => { const t1 = serializer.parse('/one;a=1/(two;b=2//left:three)'); const t2 = serializer.parse('/one;a=1/two;b=2'); expect(containsTree(t1, t2, {...subsetMatchOptions, matrixParams})).toBe(true); }, ); }); describe('subset match', () => { const matrixParams = 'subset'; it('returns true when matrix params match', () => { const t1 = serializer.parse('/a;id=15;foo=foo'); const t2 = serializer.parse('/a;id=15;foo=foo'); expect(containsTree(t1, t2, {...exactMatchOptions, matrixParams})).toBe(true); }); it('returns true when container has extra matrix params', () => { const t1 = serializer.parse('/a;id=15;foo=foo'); const t2 = serializer.parse('/a;id=15'); expect(containsTree(t1, t2, {...exactMatchOptions, matrixParams})).toBe(true); }); it('returns false when matrix params differ', () => { const t1 = serializer.parse('/a;id=15;foo=foo'); const t2 = serializer.parse('/a;abc=123'); expect(containsTree(t1, t2, {...exactMatchOptions, matrixParams})).toBe(false); }); it('returns true when matrix params match on the subset of the matched url tree', () => { const t1 = serializer.parse('/a;id=15;foo=bar/c'); const t2 = serializer.parse('/a;id=15;foo=bar'); expect(containsTree(t1, t2, {...subsetMatchOptions, matrixParams})).toBe(true); }); it( 'should return true when matrix params match on subset of urlTree match ' + 'with container paths split into multiple segments', () => { const t1 = serializer.parse('/one;a=1/(two;b=2//left:three)'); const t2 = serializer.parse('/one;a=1/two'); expect(containsTree(t1, t2, {...subsetMatchOptions, matrixParams})).toBe(true); }, ); }); }); }); });