|
| 1 | +import { fakeAsync } from '@angular/core/testing'; |
| 2 | +import { |
| 3 | + ActivatedRouteSnapshot, |
| 4 | + RouterStateSnapshot, |
| 5 | +} from '@angular/router'; |
| 6 | +import { |
| 7 | + EMPTY, |
| 8 | + Observable, |
| 9 | + of, |
| 10 | +} from 'rxjs'; |
| 11 | + |
| 12 | +import { MENUS } from './app.menus'; |
| 13 | +import { Community } from './core/shared/community.model'; |
| 14 | +import { Item } from './core/shared/item.model'; |
| 15 | +import { MenuService } from './shared/menu/menu.service'; |
| 16 | +import { MENU_PROVIDER } from './shared/menu/menu.structure'; |
| 17 | +import { MenuID } from './shared/menu/menu-id.model'; |
| 18 | +import { MenuItemType } from './shared/menu/menu-item-type.model'; |
| 19 | +import { |
| 20 | + AbstractMenuProvider, |
| 21 | + PartialMenuSection, |
| 22 | +} from './shared/menu/menu-provider.model'; |
| 23 | +import { MenuProviderService } from './shared/menu/menu-provider.service'; |
| 24 | +import { MenuRoute } from './shared/menu/menu-route.model'; |
| 25 | +import { ComColSearchMenuProvider } from './shared/menu/providers/comcol-search.menu'; |
| 26 | +import { createSuccessfulRemoteDataObject } from './shared/remote-data.utils'; |
| 27 | + |
| 28 | +/** |
| 29 | + * CLARIN/LINDAT: guards for the scoped-search menu entry (dtq-dev PR #1326, card FE-22). |
| 30 | + * |
| 31 | + * The provider itself is covered by comcol-search.menu.spec.ts, but nothing covered the two things |
| 32 | + * that make the feature correct in the app: that it is registered in app.menus.ts at all, and that |
| 33 | + * it is registered for Community/Collection pages *only*. The source commit asserted the latter |
| 34 | + * against the 7.x DSOEditMenuResolver ('should not return Community/Collection-specific entries' -> |
| 35 | + * `expect(menu.find(e => e.id === 'search-dso')).toBeFalsy()`); on v9 the resolver is gone and the |
| 36 | + * equivalent guard is this one. |
| 37 | + */ |
| 38 | +describe('MENUS - scoped-search entry (ComColSearchMenuProvider)', () => { |
| 39 | + |
| 40 | + /** |
| 41 | + * The resolved MENU_PROVIDER entry that app.menus.ts produced for ComColSearchMenuProvider. |
| 42 | + * buildMenuStructure() emits one such object per registered provider; its useFactory stamps the |
| 43 | + * menu id, the parent id and the active routes onto the provider instance. |
| 44 | + */ |
| 45 | + function findRegistration(): any { |
| 46 | + return (MENUS as any[]).find((provider: any) => |
| 47 | + provider !== null |
| 48 | + && typeof provider === 'object' |
| 49 | + && provider.provide === MENU_PROVIDER |
| 50 | + && Array.isArray(provider.deps) |
| 51 | + && provider.deps[0] === ComColSearchMenuProvider, |
| 52 | + ); |
| 53 | + } |
| 54 | + |
| 55 | + /** |
| 56 | + * A ComColSearchMenuProvider configured exactly the way app.menus.ts configures it. |
| 57 | + */ |
| 58 | + function configuredProvider(): AbstractMenuProvider { |
| 59 | + return findRegistration().useFactory(new ComColSearchMenuProvider()); |
| 60 | + } |
| 61 | + |
| 62 | + describe('registration in app.menus.ts', () => { |
| 63 | + |
| 64 | + it('should register ComColSearchMenuProvider in the DSO edit menu', () => { |
| 65 | + const registration = findRegistration(); |
| 66 | + |
| 67 | + expect(registration).toBeTruthy(); |
| 68 | + expect(configuredProvider().menuID).toEqual(MenuID.DSO_EDIT); |
| 69 | + }); |
| 70 | + |
| 71 | + it('should register it as a sub-provider so it renders inside the options dropdown', () => { |
| 72 | + expect(configuredProvider().parentID).toBeTruthy(); |
| 73 | + }); |
| 74 | + |
| 75 | + it('should activate it on community and collection pages but not on item pages', () => { |
| 76 | + const activePaths = configuredProvider().activePaths; |
| 77 | + |
| 78 | + expect(activePaths).toContain(MenuRoute.COMMUNITY_PAGE); |
| 79 | + expect(activePaths).toContain(MenuRoute.COLLECTION_PAGE); |
| 80 | + expect(activePaths).not.toContain(MenuRoute.ITEM_PAGE); |
| 81 | + }); |
| 82 | + |
| 83 | + }); |
| 84 | + |
| 85 | + describe('sections resolved for a route', () => { |
| 86 | + |
| 87 | + /** |
| 88 | + * A second provider, active on every route, whose only job is to prove that the resolution |
| 89 | + * pipeline actually ran. Without it the "no scoped-search section on an item page" expectation |
| 90 | + * would also hold if resolveRouteMenus silently produced nothing at all. |
| 91 | + */ |
| 92 | + class AlwaysOnMenuProvider extends AbstractMenuProvider { |
| 93 | + menuID = MenuID.DSO_EDIT; |
| 94 | + menuProviderId = 'always-on'; |
| 95 | + shouldPersistOnRouteChange = false; |
| 96 | + |
| 97 | + getSections(): Observable<PartialMenuSection[]> { |
| 98 | + return of([{ |
| 99 | + id: 'always-on-marker', |
| 100 | + visible: true, |
| 101 | + model: { |
| 102 | + type: MenuItemType.TEXT, |
| 103 | + text: 'always-on-marker', |
| 104 | + }, |
| 105 | + }] as PartialMenuSection[]); |
| 106 | + } |
| 107 | + } |
| 108 | + |
| 109 | + let menuService: any; |
| 110 | + let menuProviderService: MenuProviderService; |
| 111 | + |
| 112 | + const community = Object.assign(new Community(), { uuid: 'test-community-uuid' }); |
| 113 | + const item = Object.assign(new Item(), { uuid: 'test-item-uuid' }); |
| 114 | + |
| 115 | + beforeEach(() => { |
| 116 | + menuService = jasmine.createSpyObj('MenuService', { |
| 117 | + addSection: {}, |
| 118 | + removeSection: {}, |
| 119 | + getMenu: of({ id: MenuID.DSO_EDIT }), |
| 120 | + getNonPersistentMenuSections: of([]), |
| 121 | + }); |
| 122 | + menuProviderService = new MenuProviderService( |
| 123 | + [configuredProvider(), new AlwaysOnMenuProvider()], |
| 124 | + menuService as MenuService, |
| 125 | + { events: EMPTY } as any, |
| 126 | + ); |
| 127 | + }); |
| 128 | + |
| 129 | + function resolve(menuRoute: MenuRoute, dso: Community | Item): void { |
| 130 | + const route = { data: { menuRoute, dso: createSuccessfulRemoteDataObject(dso) } }; |
| 131 | + menuProviderService.resolveRouteMenus( |
| 132 | + route as unknown as ActivatedRouteSnapshot, |
| 133 | + { url: '/test-url' } as unknown as RouterStateSnapshot, |
| 134 | + false, |
| 135 | + ).subscribe(); |
| 136 | + } |
| 137 | + |
| 138 | + function addedScopedSearchSections(): any[] { |
| 139 | + return menuService.addSection.calls.allArgs() |
| 140 | + .filter(([, section]: [MenuID, any]) => section?.model?.link === '/search'); |
| 141 | + } |
| 142 | + |
| 143 | + it('should add the scoped-search section on a community page', fakeAsync(() => { |
| 144 | + resolve(MenuRoute.COMMUNITY_PAGE, community); |
| 145 | + |
| 146 | + const sections = addedScopedSearchSections(); |
| 147 | + expect(sections.length).toBe(1); |
| 148 | + expect(sections[0][0]).toEqual(MenuID.DSO_EDIT); |
| 149 | + expect(sections[0][1].model.queryParams.scope).toEqual('test-community-uuid'); |
| 150 | + })); |
| 151 | + |
| 152 | + it('should not add the scoped-search section on an item page', fakeAsync(() => { |
| 153 | + resolve(MenuRoute.ITEM_PAGE, item); |
| 154 | + |
| 155 | + // the pipeline ran - the always-on provider's section was added |
| 156 | + expect(menuService.addSection).toHaveBeenCalledWith( |
| 157 | + MenuID.DSO_EDIT, |
| 158 | + jasmine.objectContaining({ id: 'always-on-marker' }), |
| 159 | + ); |
| 160 | + // ... but the scoped-search section was not |
| 161 | + expect(addedScopedSearchSections()).toEqual([]); |
| 162 | + })); |
| 163 | + |
| 164 | + }); |
| 165 | + |
| 166 | +}); |
0 commit comments