mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-16 10:33:43 +08:00
The SSR router relies on wildcard matching rather than params. This commit refactors the stored routes by removing the `:params`.
279 lines
10 KiB
TypeScript
279 lines
10 KiB
TypeScript
/**
|
|
* @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 { RenderMode } from '../../src/routes/route-config';
|
|
import { RouteTree } from '../../src/routes/route-tree';
|
|
|
|
describe('RouteTree', () => {
|
|
let routeTree: RouteTree;
|
|
|
|
beforeEach(() => {
|
|
routeTree = new RouteTree();
|
|
});
|
|
|
|
describe('toObject and fromObject', () => {
|
|
it('should convert the route tree to a nested object and back', () => {
|
|
routeTree.insert('/home', { redirectTo: '/home-page', renderMode: RenderMode.Server });
|
|
routeTree.insert('/about', { redirectTo: '/about-page', renderMode: RenderMode.Server });
|
|
routeTree.insert('/products/:id', { renderMode: RenderMode.Server });
|
|
routeTree.insert('/api/details', {
|
|
redirectTo: '/api/details-page',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
|
|
const routeTreeObj = routeTree.toObject();
|
|
expect(routeTreeObj).toEqual([
|
|
{ redirectTo: '/home-page', route: '/home', renderMode: RenderMode.Server },
|
|
{ redirectTo: '/about-page', route: '/about', renderMode: RenderMode.Server },
|
|
{ route: '/products/*', renderMode: RenderMode.Server },
|
|
{ redirectTo: '/api/details-page', route: '/api/details', renderMode: RenderMode.Server },
|
|
]);
|
|
|
|
const newRouteTree = RouteTree.fromObject(routeTreeObj);
|
|
expect(newRouteTree.match('/home')).toEqual({
|
|
redirectTo: '/home-page',
|
|
route: '/home',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
expect(newRouteTree.match('/about')).toEqual({
|
|
redirectTo: '/about-page',
|
|
route: '/about',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
expect(newRouteTree.match('/products/123')).toEqual({
|
|
route: '/products/*',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
expect(newRouteTree.match('/api/details')).toEqual({
|
|
redirectTo: '/api/details-page',
|
|
route: '/api/details',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
});
|
|
|
|
it('should handle complex route structures when converting to and from object', () => {
|
|
routeTree.insert('/shop/categories/:category/products/:id', {
|
|
redirectTo: '/shop/products',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
routeTree.insert('/shop/cart', {
|
|
redirectTo: '/shop/cart-page',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
|
|
const routeTreeObj = routeTree.toObject();
|
|
const newRouteTree = RouteTree.fromObject(routeTreeObj);
|
|
|
|
expect(newRouteTree.match('/shop/categories/electronics/products/123')).toEqual({
|
|
redirectTo: '/shop/products',
|
|
route: '/shop/categories/*/products/*',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
expect(newRouteTree.match('/shop/cart')).toEqual({
|
|
redirectTo: '/shop/cart-page',
|
|
route: '/shop/cart',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
});
|
|
|
|
it('should construct a RouteTree from a nested object representation', () => {
|
|
const routeTreeObj = [
|
|
{ redirectTo: '/home-page', route: '/home', renderMode: RenderMode.Server },
|
|
{ redirectTo: '/about-page', route: '/about', renderMode: RenderMode.Server },
|
|
{
|
|
redirectTo: '/api/details-page',
|
|
route: '/api/*/details',
|
|
renderMode: RenderMode.Server,
|
|
},
|
|
];
|
|
|
|
const newRouteTree = RouteTree.fromObject(routeTreeObj);
|
|
expect(newRouteTree.match('/home')).toEqual({
|
|
redirectTo: '/home-page',
|
|
route: '/home',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
expect(newRouteTree.match('/about')).toEqual({
|
|
redirectTo: '/about-page',
|
|
route: '/about',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
expect(newRouteTree.match('/api/users/details')).toEqual({
|
|
redirectTo: '/api/details-page',
|
|
route: '/api/*/details',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
expect(newRouteTree.match('/nonexistent')).toBeUndefined();
|
|
});
|
|
|
|
it('should handle an empty RouteTree correctly', () => {
|
|
const routeTreeObj = routeTree.toObject();
|
|
expect(routeTreeObj).toEqual([]);
|
|
|
|
const newRouteTree = RouteTree.fromObject(routeTreeObj);
|
|
expect(newRouteTree.match('/any-path')).toBeUndefined();
|
|
});
|
|
|
|
it('should preserve insertion order when converting to and from object', () => {
|
|
routeTree.insert('/first', { renderMode: RenderMode.Server });
|
|
routeTree.insert('/:id', { renderMode: RenderMode.Server });
|
|
routeTree.insert('/second', { renderMode: RenderMode.Server });
|
|
|
|
const routeTreeObj = routeTree.toObject();
|
|
expect(routeTreeObj).toEqual([
|
|
{ route: '/first', renderMode: RenderMode.Server },
|
|
{ route: '/*', renderMode: RenderMode.Server },
|
|
{ route: '/second', renderMode: RenderMode.Server },
|
|
]);
|
|
|
|
const newRouteTree = RouteTree.fromObject(routeTreeObj);
|
|
expect(newRouteTree.match('/first')).toEqual({
|
|
route: '/first',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
expect(newRouteTree.match('/second')).toEqual({
|
|
route: '/*',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
expect(newRouteTree.match('/third')).toEqual({
|
|
route: '/*',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('match', () => {
|
|
it('should handle empty routes', () => {
|
|
routeTree.insert('', { renderMode: RenderMode.Server });
|
|
expect(routeTree.match('')).toEqual({ route: '', renderMode: RenderMode.Server });
|
|
});
|
|
|
|
it('should insert and match basic routes', () => {
|
|
routeTree.insert('/home', { renderMode: RenderMode.Server });
|
|
routeTree.insert('/about', { renderMode: RenderMode.Server });
|
|
|
|
expect(routeTree.match('/home')).toEqual({ route: '/home', renderMode: RenderMode.Server });
|
|
expect(routeTree.match('/about')).toEqual({ route: '/about', renderMode: RenderMode.Server });
|
|
expect(routeTree.match('/contact')).toBeUndefined();
|
|
});
|
|
|
|
it('should handle wildcard segments', () => {
|
|
routeTree.insert('/api/users', { renderMode: RenderMode.Server });
|
|
routeTree.insert('/api/products', { renderMode: RenderMode.Server });
|
|
routeTree.insert('/api/*/details', { renderMode: RenderMode.Server });
|
|
|
|
expect(routeTree.match('/api/users')).toEqual({
|
|
route: '/api/users',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
expect(routeTree.match('/api/products')).toEqual({
|
|
route: '/api/products',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
expect(routeTree.match('/api/orders/details')).toEqual({
|
|
route: '/api/*/details',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
});
|
|
|
|
it('should handle catch all (double wildcard) segments', () => {
|
|
routeTree.insert('/api/users', { renderMode: RenderMode.Server });
|
|
routeTree.insert('/api/*/users/**', { renderMode: RenderMode.Server });
|
|
routeTree.insert('/api/**', { renderMode: RenderMode.Server });
|
|
|
|
expect(routeTree.match('/api/users')).toEqual({
|
|
route: '/api/users',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
expect(routeTree.match('/api/products')).toEqual({
|
|
route: '/api/**',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
expect(routeTree.match('/api/info/users/details')).toEqual({
|
|
route: '/api/*/users/**',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
expect(routeTree.match('/api/user/details')).toEqual({
|
|
route: '/api/**',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
});
|
|
|
|
it('should prioritize earlier insertions in case of conflicts', () => {
|
|
routeTree.insert('/blog/*', { renderMode: RenderMode.Server });
|
|
routeTree.insert('/blog/article', { redirectTo: 'blog', renderMode: RenderMode.Server });
|
|
|
|
expect(routeTree.match('/blog/article')).toEqual({
|
|
route: '/blog/*',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
});
|
|
|
|
it('should handle parameterized segments as wildcards', () => {
|
|
routeTree.insert('/users/:id', { renderMode: RenderMode.Server });
|
|
expect(routeTree.match('/users/123')).toEqual({
|
|
route: '/users/*',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
});
|
|
|
|
it('should handle complex route structures', () => {
|
|
routeTree.insert('/shop/categories/:category', { renderMode: RenderMode.Server });
|
|
routeTree.insert('/shop/categories/:category/products/:id', {
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
|
|
expect(routeTree.match('/shop/categories/electronics')).toEqual({
|
|
route: '/shop/categories/*',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
expect(routeTree.match('/shop/categories/electronics/products/456')).toEqual({
|
|
route: '/shop/categories/*/products/*',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
});
|
|
|
|
it('should return undefined for unmatched routes', () => {
|
|
routeTree.insert('/foo', { renderMode: RenderMode.Server });
|
|
expect(routeTree.match('/bar')).toBeUndefined();
|
|
});
|
|
|
|
it('should handle multiple wildcards in a path', () => {
|
|
routeTree.insert('/a/*/b/*/c', { renderMode: RenderMode.Server });
|
|
expect(routeTree.match('/a/1/b/2/c')).toEqual({
|
|
route: '/a/*/b/*/c',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
});
|
|
|
|
it('should handle trailing slashes', () => {
|
|
routeTree.insert('/foo/', { renderMode: RenderMode.Server });
|
|
expect(routeTree.match('/foo')).toEqual({ route: '/foo', renderMode: RenderMode.Server });
|
|
expect(routeTree.match('/foo/')).toEqual({ route: '/foo', renderMode: RenderMode.Server });
|
|
});
|
|
|
|
it('should handle case-sensitive matching', () => {
|
|
routeTree.insert('/case', { renderMode: RenderMode.Server });
|
|
expect(routeTree.match('/CASE')).toBeUndefined();
|
|
});
|
|
|
|
it('should handle routes with special characters', () => {
|
|
routeTree.insert('/path with spaces', { renderMode: RenderMode.Server });
|
|
routeTree.insert('/path/with/slashes', { renderMode: RenderMode.Server });
|
|
expect(routeTree.match('/path with spaces')).toEqual({
|
|
route: '/path with spaces',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
expect(routeTree.match('/path/with/slashes')).toEqual({
|
|
route: '/path/with/slashes',
|
|
renderMode: RenderMode.Server,
|
|
});
|
|
});
|
|
});
|
|
});
|