Skip to content

Commit

Permalink
refactor: content page url should contain full content page path
Browse files Browse the repository at this point in the history
  • Loading branch information
Eisie96 authored and shauke committed Jun 15, 2022
1 parent 2201c44 commit a183b74
Show file tree
Hide file tree
Showing 15 changed files with 570 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ContentPageTree, ContentPageTreeElement } from 'ish-core/models/content
export interface ContentPageTreeView extends ContentPageTreeElement {
parent: string;
children: ContentPageTreeView[];
pathElements: ContentPageTreeElement[];
}

/**
Expand Down Expand Up @@ -38,10 +39,12 @@ function getContentPageTreeElements(
}

const parent = tree.nodes[elementId].path[tree.nodes[elementId].path.length - 2];

treeElements.push({
...tree.nodes[elementId],
parent,
children: [],
pathElements: tree.nodes[elementId].path.map(p => tree.nodes[p]),
});

return treeElements;
Expand Down
2 changes: 2 additions & 0 deletions src/app/core/pipes.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import { MakeHrefPipe } from './pipes/make-href.pipe';
import { SanitizePipe } from './pipes/sanitize.pipe';
import { ServerSettingPipe } from './pipes/server-setting.pipe';
import { CategoryRoutePipe } from './routing/category/category-route.pipe';
import { ContentPageRoutePipe } from './routing/content-page/content-page-route.pipe';
import { ProductRoutePipe } from './routing/product/product-route.pipe';

const pipes = [
AttributeToStringPipe,
CategoryRoutePipe,
ContentPageRoutePipe,
DatePipe,
FeatureTogglePipe,
HighlightPipe,
Expand Down
12 changes: 12 additions & 0 deletions src/app/core/routing/content-page/content-page-route.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Pipe, PipeTransform } from '@angular/core';

import { ContentPageTreeView } from 'ish-core/models/content-page-tree-view/content-page-tree-view.model';

import { generateContentPageUrl } from './content-page.route';

@Pipe({ name: 'ishContentPageRoute', pure: true })
export class ContentPageRoutePipe implements PipeTransform {
transform(page: ContentPageTreeView): string {
return generateContentPageUrl(page);
}
}
112 changes: 112 additions & 0 deletions src/app/core/routing/content-page/content-page-route.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { TestBed } from '@angular/core/testing';
import { Router, UrlMatchResult, UrlSegment } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';

import { createContentPageTreeView } from 'ish-core/models/content-page-tree-view/content-page-tree-view.model';
import { ContentPageTreeHelper } from 'ish-core/models/content-page-tree/content-page-tree.helper';
import { ContentPageTreeElement } from 'ish-core/models/content-page-tree/content-page-tree.model';

import { generateContentPageUrl, matchContentRoute } from './content-page.route';

describe('Content Page Route', () => {
const topElement: ContentPageTreeElement = { contentPageId: 'top', name: 'top layer', path: ['top'] };
const secondElement: ContentPageTreeElement = {
contentPageId: 'second',
name: 'second layer',
path: ['top', 'second'],
};
const bottomElement: ContentPageTreeElement = {
contentPageId: 'bottom',
name: 'bottom layer',
path: ['top', 'second', 'bottom'],
};

expect.addSnapshotSerializer({
test: val => val?.consumed && val.posParams,
print: (val: UrlMatchResult, serialize) =>
serialize(
Object.keys(val.posParams)
.map(key => ({ [key]: val.posParams[key].path }))
.reduce((acc, v) => ({ ...acc, ...v }), {})
),
});

let wrap: (url: string) => UrlSegment[];

beforeEach(() => {
TestBed.configureTestingModule({ imports: [RouterTestingModule] });
const router = TestBed.inject(Router);
wrap = url => {
const primary = router.parseUrl(url).root.children.primary;
return primary ? primary.segments : [];
};
});

describe('without anything', () => {
it('should be created', () => {
expect(generateContentPageUrl(undefined)).toMatchInlineSnapshot(`"/"`);
});

it('should not be a match for matcher', () => {
expect(matchContentRoute(wrap(generateContentPageUrl(undefined)))).toMatchInlineSnapshot(`undefined`);
});
});

describe('with top level content page without name', () => {
const topLevelView = createContentPageTreeView(
ContentPageTreeHelper.single({ ...topElement, name: undefined }),
'top',
'top'
);

it('should be created', () => {
expect(generateContentPageUrl(topLevelView)).toMatchInlineSnapshot(`"/pgtop"`);
});

it('should be a match for matcher', () => {
expect(matchContentRoute(wrap(generateContentPageUrl(topLevelView)))).toMatchInlineSnapshot(`
Object {
"contentPageId": "top",
}
`);
});
});

describe('with top level content page', () => {
const topLevelView = createContentPageTreeView(ContentPageTreeHelper.single(topElement), 'top', 'top');

it('should be created', () => {
expect(generateContentPageUrl(topLevelView)).toMatchInlineSnapshot(`"/top-layer-pgtop"`);
});

it('should not be a match for matcher', () => {
expect(matchContentRoute(wrap(generateContentPageUrl(topLevelView)))).toMatchInlineSnapshot(`
Object {
"contentPageId": "top",
}
`);
});
});

describe('with deep content page hierachy', () => {
const topLevelTree = ContentPageTreeHelper.single(topElement);
const secondLevelTree = ContentPageTreeHelper.add(topLevelTree, secondElement);
const completeTree = ContentPageTreeHelper.add(secondLevelTree, bottomElement);

const bottomLevelView = createContentPageTreeView(completeTree, 'bottom', 'bottom');

it('should be created', () => {
expect(generateContentPageUrl(bottomLevelView)).toMatchInlineSnapshot(
`"/top-layer/second-layer/bottom-layer-pgbottom"`
);
});

it('should not be a match for matcher', () => {
expect(matchContentRoute(wrap(generateContentPageUrl(bottomLevelView)))).toMatchInlineSnapshot(`
Object {
"contentPageId": "bottom",
}
`);
});
});
});
79 changes: 79 additions & 0 deletions src/app/core/routing/content-page/content-page.route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { UrlMatchResult, UrlSegment } from '@angular/router';
import { MonoTypeOperatorFunction, filter } from 'rxjs';

import { ContentPageTreeView } from 'ish-core/models/content-page-tree-view/content-page-tree-view.model';
import { ContentPageTreeElement } from 'ish-core/models/content-page-tree/content-page-tree.model';
import { CoreState } from 'ish-core/store/core/core-store';
import { selectRouteParam } from 'ish-core/store/core/router';
import { sanitizeSlugData } from 'ish-core/utils/routing';

/**
* generate a localized content page slug
*
* @param page content page element for slug
* @returns localized, formatted content page slug
*/
function generateLocalizedContentPageSlug(page: ContentPageTreeElement) {
return sanitizeSlugData(page?.name);
}

// matcher to check if a given url is a content page route
const contentRouteFormat = /^\/(?!page\/.*$)(.*-)?pg(.*)$/;

/**
* check if given url is a content page route
*
* @param segments current url segments
* @returns match result if given url is a content page route or not
*/
export function matchContentRoute(segments: UrlSegment[]): UrlMatchResult {
// compatibility to old routes
if (segments && segments.length === 2 && segments[0].path === 'page') {
return {
consumed: [],
};
}

const url = `/${segments.map(s => s.path).join('/')}`;
if (contentRouteFormat.test(url)) {
const match = contentRouteFormat.exec(url);
const posParams: { [id: string]: UrlSegment } = {};
if (match[2]) {
posParams.contentPageId = new UrlSegment(match[2], {});
}
return {
consumed: [],
posParams,
};
}
return;
}

/**
* generate a localized content page url from a content page
*
* @param page content page
* @returns localized content page url
*/
export function generateContentPageUrl(page: ContentPageTreeView): string {
if (!page) {
return '/';
}

let route = '/';

// generate for each path element from the given content page hierarchy a content page slug and join them together to a complete route
route += page.pathElements?.map(p => generateLocalizedContentPageSlug(p)).join('/');

// add to content page route the content page identifier
if (route !== '/') {
route += '-';
}
route += `pg${page.contentPageId}`;

return route;
}

export function ofContentPageUrl(): MonoTypeOperatorFunction<{}> {
return source$ => source$.pipe(filter((state: CoreState) => !!selectRouteParam('contentPageId')(state)));
}
Loading

0 comments on commit a183b74

Please sign in to comment.