Skip to content

Commit 0300412

Browse files
Restore the FE-22 negative menu assertions in a unit test (#1511)
The port of #1326 (card FE-22, merged as b0c48c5) dropped the source commit's negative assertion, and nothing covered the registration itself. The source asserted, in the Item describe block of dso-edit-menu.resolver.spec.ts: const searchEntry = menu.find(entry => entry.id === 'search-dso'); expect(searchEntry).toBeFalsy(); On v9 the resolver is gone, so the equivalent guard lives on app.menus.ts: ComColSearchMenuProvider is registered with onRoute(COMMUNITY_PAGE, COLLECTION_PAGE) and must never resolve a section on an item page. Adds src/app/app.menus.spec.ts with 5 tests: three on the registration data buildMenuStructure() produces, and two behavioural ones that drive the real MenuProviderService.resolveRouteMenus() with a community route and an item route. The item-page test carries a second, always-active provider so that it fails rather than passing vacuously if the resolution pipeline stops emitting. Tests only, no production code changed. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent a2102c0 commit 0300412

1 file changed

Lines changed: 166 additions & 0 deletions

File tree

src/app/app.menus.spec.ts

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
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

Comments
 (0)