Skip to content

Commit

Permalink
fix: explicitly set breadcrumb data when using the Static Content Pag…
Browse files Browse the repository at this point in the history
…e Component (#1250)

* needed to respect navigation root when displaying the breadcrumb of CMS pages

Co-authored-by: Marcel Eisentraut <meisentraut@intershop.de>
  • Loading branch information
SGrueber and Eisie96 authored Oct 5, 2022
1 parent 752e38e commit efa1323
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 46 deletions.
16 changes: 15 additions & 1 deletion src/app/core/facades/cms.facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import { CategoryHelper } from 'ish-core/models/category/category.helper';
import { getContentInclude, loadContentInclude } from 'ish-core/store/content/includes';
import { getContentPageTree, loadContentPageTree } from 'ish-core/store/content/page-tree';
import { getContentPagelet } from 'ish-core/store/content/pagelets';
import { getContentPageLoading, getSelectedContentPage } from 'ish-core/store/content/pages';
import {
getContentPageLoading,
getSelectedContentPage,
setBreadcrumbForContentPage,
} from 'ish-core/store/content/pages';
import { getParametersProductList, loadParametersProductListFilter } from 'ish-core/store/content/parameters';
import { getViewContext, loadViewContextEntrypoint } from 'ish-core/store/content/viewcontexts';
import { getPGID } from 'ish-core/store/customer/user';
Expand Down Expand Up @@ -44,6 +48,16 @@ export class CMSFacade {
return this.store.pipe(select(getContentPageTree(rootId)));
}

/**
*
* @param rootId is taken into consideration as first element of breadcrumb for content page
*
* NOTE: use 'COMPLETE' as value of rootId to get complete available page path as breadcrumb
*/
setBreadcrumbForContentPage(rootId: string): void {
this.store.dispatch(setBreadcrumbForContentPage({ rootId }));
}

parameterProductListFilter$(categoryId?: string, productFilter?: string, scope?: string, amount?: number) {
const listConfiguration = this.getProductListConfiguration(categoryId, productFilter, scope, amount);
this.store.dispatch(
Expand Down
5 changes: 5 additions & 0 deletions src/app/core/store/content/pages/pages.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@ export const loadContentPageSuccess = createAction(
'[Content Page API] Load Content Page Success',
payload<{ page: ContentPageletEntryPoint; pagelets: ContentPagelet[] }>()
);

export const setBreadcrumbForContentPage = createAction(
'[Content Page Internal] Set Breadcrumb For Content Page',
payload<{ rootId: string }>()
);
25 changes: 17 additions & 8 deletions src/app/core/store/content/pages/pages.effects.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ import { makeHttpError } from 'ish-core/utils/dev/api-service-utils';
import { pageTree } from 'ish-core/utils/dev/test-data-utils';
import { HttpStatusCodeService } from 'ish-core/utils/http-status-code/http-status-code.service';

import { loadContentPage, loadContentPageFail, loadContentPageSuccess } from './pages.actions';
import {
loadContentPage,
loadContentPageFail,
loadContentPageSuccess,
setBreadcrumbForContentPage,
} from './pages.actions';
import { PagesEffects } from './pages.effects';

describe('Pages Effects', () => {
Expand Down Expand Up @@ -118,36 +123,40 @@ describe('Pages Effects', () => {
});

describe('setBreadcrumbForContentPage$', () => {
const tree1 = { contentPageId: '1', name: 'page 1', path: ['1'] } as ContentPageTreeElement;
const tree2 = { contentPageId: '1.1', name: 'page 1.1', path: ['1', '1.1'] } as ContentPageTreeElement;
const tree1 = { contentPageId: '1', path: ['1'], name: 'Page 1' } as ContentPageTreeElement;
const tree2 = { contentPageId: '1.1', path: ['1', '1.1'], name: 'Page 1.1' } as ContentPageTreeElement;

beforeEach(fakeAsync(() => {
router.navigateByUrl('/page/1.1');
tick(500);
store.dispatch(
loadContentPageSuccess({
page: { id: '1.1', displayName: 'page 1.1' } as ContentPageletEntryPoint,
page: { id: '1.1', displayName: 'Page 1.1' } as ContentPageletEntryPoint,
pagelets: [],
})
);
}));

it('should set breadcrumb if selected content page is not part of a page tree', done => {
it('should set page breadcrumb data without path if no page tree information is available', done => {
actions$ = of(setBreadcrumbForContentPage({ rootId: '1' }));

effects.setBreadcrumbForContentPage$.subscribe(action => {
expect(action).toMatchInlineSnapshot(`
[Viewconf Internal] Set Breadcrumb Data:
breadcrumbData: [{"key":"page 1.1"}]
breadcrumbData: [{"key":"Page 1.1"}]
`);
done();
});
});

it('should set page breadcrumb if selected content page is part of a page tree', done => {
it('should set page breadcrumb with path if selected content page is part of a page tree', done => {
store.dispatch(loadContentPageTreeSuccess({ pagetree: pageTree([tree1, tree2]) }));
actions$ = of(setBreadcrumbForContentPage({ rootId: '1' }));

effects.setBreadcrumbForContentPage$.subscribe(action => {
expect(action).toMatchInlineSnapshot(`
[Viewconf Internal] Set Breadcrumb Data:
breadcrumbData: [{"key":"page 1","link":"/page-1-pg1"},{"key":"page 1.1"}]
breadcrumbData: [{"key":"Page 1","link":"/page-1-pg1"},{"key":"Page 1.1"}]
`);
done();
});
Expand Down
22 changes: 17 additions & 5 deletions src/app/core/store/content/pages/pages.effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { Store, select } from '@ngrx/store';
import { from } from 'rxjs';
import { concatMap, map, mergeMap } from 'rxjs/operators';
import { concatMap, map, mergeMap, switchMap } from 'rxjs/operators';

import { CMSService } from 'ish-core/services/cms/cms.service';
import { selectRouteParam } from 'ish-core/store/core/router';
Expand All @@ -16,7 +16,12 @@ import {
whenTruthy,
} from 'ish-core/utils/operators';

import { loadContentPage, loadContentPageFail, loadContentPageSuccess } from './pages.actions';
import {
loadContentPage,
loadContentPageFail,
loadContentPageSuccess,
setBreadcrumbForContentPage,
} from './pages.actions';
import { getBreadcrumbForContentPage } from './pages.selectors';

@Injectable()
Expand Down Expand Up @@ -58,9 +63,16 @@ export class PagesEffects {
);

setBreadcrumbForContentPage$ = createEffect(() =>
this.store.pipe(select(getBreadcrumbForContentPage)).pipe(
whenTruthy(),
map(breadcrumbData => setBreadcrumbData({ breadcrumbData }))
this.actions$.pipe(
ofType(setBreadcrumbForContentPage),
mapToPayloadProperty('rootId'),
// eslint-disable-next-line rxjs/no-unsafe-switchmap
switchMap(rootId =>
this.store.pipe(
select(getBreadcrumbForContentPage(rootId)),
map(breadcrumbData => setBreadcrumbData({ breadcrumbData }))
)
)
)
);
}
101 changes: 80 additions & 21 deletions src/app/core/store/content/pages/pages.selectors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,60 +51,119 @@ describe('Pages Selectors', () => {
}));
});

describe('getSelectedContentPageBreadcrumbData', () => {
const tree1 = { contentPageId: '1', path: ['1'], name: '1' } as ContentPageTreeElement;
const tree2 = { contentPageId: '1.1', path: ['1', '1.1'], name: '1.1' } as ContentPageTreeElement;
describe('getBreadcrumbForContentPage', () => {
const tree1 = { contentPageId: '1', path: ['1'], name: 'Page 1' } as ContentPageTreeElement;
const tree2 = { contentPageId: '1.1', path: ['1', '1.1'], name: 'Page 1.1' } as ContentPageTreeElement;
const tree3 = { contentPageId: '1.1.1', path: ['1', '1.1', '1.1.1'], name: 'Page 1.1.1' } as ContentPageTreeElement;
const tree4 = { contentPageId: '1.1.2', path: ['1', '1.1', '1.1.2'], name: 'Page 1.1.2' } as ContentPageTreeElement;

beforeEach(() => {
store$.dispatch(loadContentPageTreeSuccess({ pagetree: pageTree([tree1, tree2, tree3, tree4]) }));
});

it('should return just the page name, if no rootId is given', fakeAsync(() => {
store$.dispatch(
loadContentPageSuccess({
page: { id: '1.1', displayName: 'page-1.1' } as ContentPageletEntryPoint,
page: { id: '1.1', displayName: 'Page 1.1' } as ContentPageletEntryPoint,
pagelets: [],
})
);
router.navigateByUrl('/any;contentPageId=1.1');
tick(500);
expect(getBreadcrumbForContentPage(undefined)(store$.state)).toMatchInlineSnapshot(`
Array [
Object {
"key": "Page 1.1",
},
]
`);
}));

it('should return just the page name, if a rootId, that is not in the path, is given', fakeAsync(() => {
store$.dispatch(
loadContentPageSuccess({ page: { id: '1', displayName: 'page-1' } as ContentPageletEntryPoint, pagelets: [] })
loadContentPageSuccess({
page: { id: '1.1', displayName: 'Page 1.1' } as ContentPageletEntryPoint,
pagelets: [],
})
);
});
router.navigateByUrl('/any;contentPageId=1.1');
tick(500);
expect(getBreadcrumbForContentPage('1.1.2')(store$.state)).toMatchInlineSnapshot(`
Array [
Object {
"key": "Page 1.1",
},
]
`);
}));

it('should return undefined, if selected content page is not part of a page tree', fakeAsync(() => {
it('should return complete BreadcrumbData, if "COMPLETE" is given as rootId if rootId is not known', fakeAsync(() => {
store$.dispatch(
loadContentPageSuccess({
page: { id: '1.1', displayName: 'Page 1.1' } as ContentPageletEntryPoint,
pagelets: [],
})
);
router.navigateByUrl('/any;contentPageId=1.1');
tick(500);
expect(getBreadcrumbForContentPage(store$.state)).toMatchInlineSnapshot(`
expect(getBreadcrumbForContentPage('COMPLETE')(store$.state)).toMatchInlineSnapshot(`
Array [
Object {
"key": "page-1.1",
"key": "Page 1",
"link": "/page-1-pg1",
},
Object {
"key": "Page 1.1",
"link": undefined,
},
]
`);
}));

it('should return BreadcrumbData, if selected content page is part of a page tree', fakeAsync(() => {
store$.dispatch(loadContentPageTreeSuccess({ pagetree: pageTree([tree1, tree2]) }));
router.navigateByUrl('/any;contentPageId=1');
it('should return complete BreadcrumbData, if root rootId is given', fakeAsync(() => {
store$.dispatch(
loadContentPageSuccess({
page: { id: '1.1.1', displayName: 'Page 1.1.1' } as ContentPageletEntryPoint,
pagelets: [],
})
);
router.navigateByUrl('/any;contentPageId=1.1.1');
tick(500);
expect(getBreadcrumbForContentPage(store$.state)).toMatchInlineSnapshot(`
expect(getBreadcrumbForContentPage('1')(store$.state)).toMatchInlineSnapshot(`
Array [
Object {
"key": "1",
"key": "Page 1",
"link": "/page-1-pg1",
},
Object {
"key": "Page 1.1",
"link": "/page-1/page-1.1-pg1.1",
},
Object {
"key": "Page 1.1.1",
"link": undefined,
},
]
`);
}));

it('should return BreadcrumbData, if selected content page is part of a page tree', fakeAsync(() => {
store$.dispatch(loadContentPageTreeSuccess({ pagetree: pageTree([tree1, tree2]) }));
router.navigateByUrl('/any;contentPageId=1.1');
it('should return partial BreadcrumbData, if not root is given as rootId', fakeAsync(() => {
store$.dispatch(
loadContentPageSuccess({
page: { id: '1.1.1', displayName: 'Page 1.1.1' } as ContentPageletEntryPoint,
pagelets: [],
})
);
router.navigateByUrl('/any;contentPageId=1.1.1');
tick(500);
expect(getBreadcrumbForContentPage(store$.state)).toMatchInlineSnapshot(`
expect(getBreadcrumbForContentPage('1.1')(store$.state)).toMatchInlineSnapshot(`
Array [
Object {
"key": "1",
"link": "/1-pg1",
"key": "Page 1.1",
"link": "/page-1/page-1.1-pg1.1",
},
Object {
"key": "1.1",
"key": "Page 1.1.1",
"link": undefined,
},
]
Expand Down
51 changes: 40 additions & 11 deletions src/app/core/store/content/pages/pages.selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,43 @@ export const getSelectedContentPage = createSelector(getPageEntities, selectRout
createContentPageletEntryPointView(pages[id])
);

export const getBreadcrumbForContentPage = createSelectorFactory<object, BreadcrumbItem[]>(projector =>
resultMemoize(projector, isEqual)
)(getPageTree, getSelectedContentPage, (pagetree: ContentPageTree, contentPage: ContentPageletEntryPointView) =>
pagetree.nodes[contentPage?.id]
? (pagetree.nodes[contentPage.id].path.map((item, i, path) => ({
key: pagetree.nodes[item].name,
link:
i !== path.length - 1 ? generateContentPageUrl(createContentPageTreeView(pagetree, item, item)) : undefined,
})) as BreadcrumbItem[])
: ([{ key: contentPage?.displayName }] as BreadcrumbItem[])
);
export const getBreadcrumbForContentPage = (rootId: string) =>
createSelectorFactory<object, BreadcrumbItem[]>(projector => resultMemoize(projector, isEqual))(
getSelectedContentPage,
getPageTree,
(contentPage: ContentPageletEntryPointView, pagetree: ContentPageTree) => {
// set default breadcrumb data: just the selected content page name
let breadcrumbData = [{ key: contentPage?.displayName }] as BreadcrumbItem[];
// initialize root flag (no root yet)
let gotRoot = false;

// if 'COMPLETE' is used as rootId the complete available page path is returned as breadcrumb data
// (in case we have no explicit root information but the full path is wanted)
if (rootId === 'COMPLETE') {
gotRoot = true;
breadcrumbData = [];
}

// determine if pagetree information is available for the selected content page
if (pagetree.nodes[contentPage?.id]) {
pagetree.nodes[contentPage.id].path.forEach((item, i, path) => {
// check if we are at the wanted path element for the root
if (item === rootId) {
gotRoot = true;
breadcrumbData = [];
}
// push breadcrumb data once we have a root
if (gotRoot) {
breadcrumbData.push({
key: pagetree.nodes[item].name,
link:
i !== path.length - 1
? generateContentPageUrl(createContentPageTreeView(pagetree, item, item))
: undefined,
});
}
});
}
return breadcrumbData;
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,8 @@ export class CMSStaticPageComponent implements CMSComponent, OnChanges {
this.pagelet.numberParam('NavigationDepth')
);
}

// explicitly set breadcrumb data for content pages that use the Static Content Page Component
this.cmsFacade.setBreadcrumbForContentPage(this.pagelet?.stringParam('NavigationRoot'));
}
}

0 comments on commit efa1323

Please sign in to comment.