Skip to content

Commit 668cf32

Browse files
committed
test: menu harnesses and refacto
1 parent 7e5e952 commit 668cf32

13 files changed

+246
-501
lines changed

library/src/lib/menu-item-node.component.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ import { openCloseAnimation, rotateAnimation } from './menu-item.animations';
1919
<ul [@openClose]="isOpen">
2020
<ng-container *ngFor="let childItem of menuItem.children; let last = last">
2121
<li
22+
asm-menu-item
2223
*ngIf="menuItemRoleService.showItem$(childItem.roles) | async"
23-
[asm-menu-item]="childItem"
24+
[menuItem]="childItem"
2425
[level]="level + 1"
2526
[itemDisabled]="itemDisabled"
2627
(isItemActive)="isChildItemActive($event, last)"

library/src/lib/menu-item.component.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { MenuItem } from './sidebar-menu.interface';
99

1010
@Component({
1111
// tslint:disable-next-line:component-selector
12-
selector: 'li[asm-menu-item]',
12+
selector: 'li[asm-menu-item][menuItem]',
1313
template: `
1414
<div
1515
*ngIf="{ disabled: (menuItemRoleService.disableItem$(menuItem.roles) | async) === true } as role"
@@ -34,8 +34,7 @@ import { MenuItem } from './sidebar-menu.interface';
3434
`,
3535
})
3636
export class MenuItemComponent implements OnInit, OnDestroy {
37-
// tslint:disable-next-line:no-input-rename
38-
@Input('asm-menu-item') menuItem!: MenuItem;
37+
@Input() menuItem!: MenuItem;
3938
@Input() isRootNode = true;
4039
@Input() level!: number;
4140
@Input() itemDisabled?: boolean;

library/src/lib/sidebar-menu.component.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,7 @@ import { MenuItemRoleService, Role } from './menu-item-role.service';
1111
styleUrls: ['sidebar-menu.component.scss'],
1212
template: `<ul class="asm-menu">
1313
<ng-container *ngFor="let item of menu">
14-
<li
15-
class="asm-menu-item"
16-
*ngIf="menuItemService.showItem$(item.roles) | async"
17-
[asm-menu-item]="item"
18-
[level]="0"
19-
></li>
14+
<li asm-menu-item *ngIf="menuItemService.showItem$(item.roles) | async" [menuItem]="item" [level]="0"></li>
2015
</ng-container>
2116
</ul>`,
2217
})

library/src/lib/tests/menu.harness.ts renamed to library/src/lib/testing/menu.harness.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
import { ComponentHarness, ContentContainerComponentHarness } from '@angular/cdk/testing';
1+
import { ComponentHarness } from '@angular/cdk/testing';
22
import { BaseHarnessFilters, HarnessPredicate } from '@angular/cdk/testing';
33

44
interface MenuItemHarnessFilters extends BaseHarnessFilters {
5-
/** Filters based on the text of the menu item. */
65
label?: string | RegExp;
76
}
87

9-
class MenuItemHarness extends ContentContainerComponentHarness {
10-
static hostSelector = '.asm-menu-item';
8+
class MenuItemHarness extends ComponentHarness {
9+
static hostSelector = '[asm-menu-item]';
1110

1211
getLabelElement = this.locatorFor('.asm-menu__item__label, .asm-menu__item__header');
1312
getAnchorElement = this.locatorFor('.asm-menu__item__anchor');
@@ -33,10 +32,22 @@ export class MenuHarness extends ComponentHarness {
3332
getItemsWithIcons = this.locatorForAll('.asm-menu__item__icon');
3433
getItemsWithBadges = this.locatorForAll('.asm-badges');
3534
getActivatedAnchors = this.locatorForAll('.asm-menu__item__anchor--active');
36-
getActivatedAnchorLabel = this.locatorFor('.asm-menu__item__anchor--active .asm-menu__item__label');
35+
getActivatedAnchorsLabels = this.locatorForAll('.asm-menu__item__anchor--active .asm-menu__item__label');
36+
getOpenedNodes = this.locatorForAll('.asm-menu__item__node--open');
37+
getOpenedNodesLabels = this.locatorForAll('.asm-menu__item__node--open > asm-menu-anchor .asm-menu__item__label');
3738

3839
async getItemsWith(filters: MenuItemHarnessFilters = {}): Promise<MenuItemHarness[]> {
3940
const getFilteredItems = this.locatorForAll(MenuItemHarness.with(filters));
4041
return getFilteredItems();
4142
}
43+
44+
async getItemWith(filters: MenuItemHarnessFilters = {}): Promise<MenuItemHarness> {
45+
const getFilteredItem = this.locatorFor(MenuItemHarness.with(filters));
46+
return getFilteredItem();
47+
}
48+
49+
async clickItemWith(filters: MenuItemHarnessFilters = {}): Promise<void> {
50+
const item = await this.getItemWith(filters);
51+
return (await item.getAnchorElement()).click();
52+
}
4253
}

library/src/lib/tests/css-selectors.spec.ts

Lines changed: 0 additions & 14 deletions
This file was deleted.

library/src/lib/tests/matcher-types.d.ts renamed to library/src/lib/tests/custom-matcher-types.d.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,5 @@
22
declare namespace jasmine {
33
interface Matchers<T> {
44
toHaveClasses(expected?: string, expectationFailOutput?: any): boolean;
5-
toHaveClassesHarness(expected?: string, expectationFailOutput?: any): boolean;
6-
toHaveText(expected?: string, expectationFailOutput?: any): boolean;
75
}
86
}
Lines changed: 1 addition & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,14 @@
1-
import { DebugElement } from '@angular/core';
21
import { TestElement } from '@angular/cdk/testing';
32

43
export const customMatchers: jasmine.CustomMatcherFactories = {
54
toHaveClasses(
65
util: jasmine.MatchersUtil,
76
customEqualityTesters: ReadonlyArray<jasmine.CustomEqualityTester>
8-
): jasmine.CustomMatcher {
9-
return {
10-
compare(actual: DebugElement, expected?: string): jasmine.CustomMatcherResult {
11-
if (!(actual.nativeElement.classList.contains instanceof Function)) {
12-
throw new Error(util.pp(actual) + ' is not a Debug element');
13-
}
14-
15-
const expectedClasses = (expected && expected.split(' ').filter((v) => v.length)) || [];
16-
17-
if (!expectedClasses.length) {
18-
throw new Error('expected value do not contain css classes');
19-
}
20-
21-
return {
22-
pass: !expectedClasses.find((cssClass) => !actual.nativeElement.classList.contains(cssClass)),
23-
message: `Expected '${actual.nativeElement.classList}' to have classes '${expected}'`,
24-
};
25-
},
26-
};
27-
},
28-
29-
toHaveClassesHarness(
30-
util: jasmine.MatchersUtil,
31-
customEqualityTesters: ReadonlyArray<jasmine.CustomEqualityTester>
327
): jasmine.CustomMatcher {
338
return {
349
compare(actual: TestElement, expected?: string): jasmine.CustomMatcherResult {
3510
if (!(actual.hasClass instanceof Function)) {
36-
throw new Error(util.pp(actual) + ' is not a Debug element');
11+
throw new Error(util.pp(actual) + ' is not a TestElement');
3712
}
3813

3914
const expectedClasses = (expected && expected.split(' ').filter((v) => v.length)) || [];
@@ -49,22 +24,4 @@ export const customMatchers: jasmine.CustomMatcherFactories = {
4924
},
5025
};
5126
},
52-
53-
toHaveText(
54-
util: jasmine.MatchersUtil,
55-
customEqualityTesters: ReadonlyArray<jasmine.CustomEqualityTester>
56-
): jasmine.CustomMatcher {
57-
return {
58-
compare(actual: DebugElement, expected?: string): jasmine.CustomMatcherResult {
59-
if (!actual.nativeElement.textContent) {
60-
throw new Error(util.pp(actual) + ' is not a Debug element');
61-
}
62-
63-
return {
64-
pass: actual.nativeElement.textContent === expected,
65-
message: `Expected '${actual.nativeElement.textContent}' to have text '${expected}'`,
66-
};
67-
},
68-
};
69-
},
7027
};
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { Component } from '@angular/core';
2+
import { Route } from '@angular/router';
3+
4+
import { Menu } from '../sidebar-menu.interface';
5+
import { SidebarMenuComponent } from '../sidebar-menu.component';
6+
import { MenuItemComponent } from '../menu-item.component';
7+
import { MenuItemNodeComponent } from '../menu-item-node.component';
8+
import { MenuItemAnchorComponent } from '../menu-item-anchor.component';
9+
import { MenuItemNodeService } from '../menu-item-node.service';
10+
import { MenuItemAnchorService } from '../menu-item-anchor.service';
11+
import { MenuItemRoleService } from '../menu-item-role.service';
12+
import { RouterTestingModule } from '@angular/router/testing';
13+
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
14+
import { TestModuleMetadata } from '@angular/core/testing';
15+
16+
@Component({})
17+
class RoutedStubComponent {}
18+
19+
export const routes: Route[] = [
20+
{
21+
path: '**',
22+
component: RoutedStubComponent,
23+
},
24+
];
25+
26+
@Component({ template: '<asm-angular-sidebar-menu [menu]="menu"></asm-angular-sidebar-menu>' })
27+
export class WrapperStubComponent {
28+
menu?: Menu;
29+
}
30+
31+
export const sharedTestingModuleFactory = (): TestModuleMetadata => ({
32+
declarations: [
33+
WrapperStubComponent,
34+
SidebarMenuComponent,
35+
MenuItemComponent,
36+
MenuItemNodeComponent,
37+
MenuItemAnchorComponent,
38+
],
39+
providers: [MenuItemNodeService, MenuItemAnchorService, MenuItemRoleService],
40+
imports: [RouterTestingModule.withRoutes(routes), NoopAnimationsModule],
41+
});
Lines changed: 28 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,13 @@
11
import { TestBed } from '@angular/core/testing';
2+
import { Router } from '@angular/router';
23
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
3-
import { SidebarMenuComponent } from '../sidebar-menu.component';
4-
5-
import { MenuHarness } from './menu.harness';
6-
import { MenuItemComponent } from '../menu-item.component';
7-
import { MenuItemNodeComponent } from '../menu-item-node.component';
8-
import { MenuItemAnchorComponent } from '../menu-item-anchor.component';
9-
import { MenuItemNodeService } from '../menu-item-node.service';
10-
import { MenuItemAnchorService } from '../menu-item-anchor.service';
11-
import { MenuItemRoleService } from '../menu-item-role.service';
12-
import { RouterTestingModule } from '@angular/router/testing';
13-
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
14-
import { Component } from '@angular/core';
15-
import { Menu, MenuItemBadge, MenuItemLeafRoute } from '../sidebar-menu.interface';
16-
import { Route, Router } from '@angular/router';
17-
import { customMatchers } from './custom.matchers.spec';
184

19-
@Component({})
20-
class RoutedStubComponent {}
5+
import { MenuHarness } from '../testing/menu.harness';
216

22-
const routes: Route[] = [
23-
{
24-
path: '',
25-
component: RoutedStubComponent,
26-
},
27-
{
28-
path: 'second',
29-
component: RoutedStubComponent,
30-
},
31-
{
32-
path: 'third',
33-
component: RoutedStubComponent,
34-
},
35-
];
7+
import { Menu, MenuItemBadge, MenuItemHeader, MenuItemLeafRoute } from '../sidebar-menu.interface';
8+
9+
import { customMatchers } from './custom.matchers.spec';
10+
import { sharedTestingModuleFactory, WrapperStubComponent } from './shared.spec';
3611

3712
const menu: Menu = [
3813
{
@@ -63,35 +38,21 @@ const menu: Menu = [
6338
},
6439
];
6540

66-
@Component({ template: '<asm-angular-sidebar-menu [menu]="menu"></asm-angular-sidebar-menu>' })
67-
class WrapperStubComponent {
68-
menu: Menu = menu;
69-
}
70-
71-
describe('menu role', () => {
41+
describe('first level', () => {
7242
let harness: MenuHarness;
7343
let router: Router;
7444

7545
beforeEach(async () => {
7646
jasmine.addMatchers(customMatchers);
7747

78-
await TestBed.configureTestingModule({
79-
declarations: [
80-
WrapperStubComponent,
81-
SidebarMenuComponent,
82-
MenuItemComponent,
83-
MenuItemNodeComponent,
84-
MenuItemAnchorComponent,
85-
],
86-
providers: [MenuItemNodeService, MenuItemAnchorService, MenuItemRoleService],
87-
imports: [RouterTestingModule.withRoutes(routes), NoopAnimationsModule],
88-
}).compileComponents();
48+
await TestBed.configureTestingModule(sharedTestingModuleFactory()).compileComponents();
8949

9050
const fixture = TestBed.createComponent(WrapperStubComponent);
9151
const loader = TestbedHarnessEnvironment.loader(fixture);
9252
harness = await loader.getHarness(MenuHarness);
9353
router = TestBed.inject(Router);
9454

55+
fixture.componentInstance.menu = menu;
9556
router.initialNavigation();
9657
fixture.detectChanges();
9758
});
@@ -110,55 +71,62 @@ describe('menu role', () => {
11071
});
11172

11273
it('should activate second menu item on navigation', async () => {
113-
await router.navigateByUrl((menu[1] as MenuItemLeafRoute).route);
74+
const itemConf = menu[1] as MenuItemLeafRoute;
75+
await router.navigateByUrl(itemConf.route);
11476

11577
const items = await harness.getActivatedAnchors();
11678
expect(items.length).toEqual(1);
11779

118-
const label = await harness.getActivatedAnchorLabel();
119-
expect(await label.text()).toEqual(menu[1].label as string);
80+
const label = await harness.getActivatedAnchorsLabels();
81+
expect(label.length).toEqual(1);
82+
expect(await label[0].text()).toEqual(itemConf.label);
12083
});
12184

12285
it('should navigate to item route on menu item click', async () => {
123-
const item = await harness.getItemsWith({ label: menu[2].label });
86+
const itemConf = menu[2] as MenuItemLeafRoute;
87+
const item = await harness.getItemsWith({ label: itemConf.label });
12488
const link = await item[0].getAnchorElement();
12589

12690
await link.click();
12791

12892
const items = await harness.getActivatedAnchors();
12993
expect(items.length).toEqual(1);
13094

131-
const label = await harness.getActivatedAnchorLabel();
132-
expect(await label.text()).toEqual(menu[2].label as string);
95+
const label = await harness.getActivatedAnchorsLabels();
96+
expect(label.length).toEqual(1);
97+
expect(await label[0].text()).toEqual(itemConf.label);
13398

134-
expect(router.url).toEqual((menu[2] as MenuItemLeafRoute).route);
99+
expect(router.url).toEqual(itemConf.route);
135100
});
136101

137102
it('should have one element with icon and two icon classes', async () => {
103+
const itemConf = menu[2] as MenuItemLeafRoute;
138104
const elements = await harness.getItemsWithIcons();
139105

140106
expect(elements.length).toEqual(1);
141-
expect(elements[0]).toHaveClassesHarness(menu[2].iconClasses);
107+
expect(elements[0]).toHaveClasses(itemConf.iconClasses);
142108
});
143109

144110
it('should have one element with two badges', async () => {
111+
const itemConf = menu[1] as MenuItemLeafRoute;
145112
const badgesConf = menu[1].badges as MenuItemBadge[];
146-
const elements = await harness.getItemsWith({ label: menu[1].label });
113+
const elements = await harness.getItemsWith({ label: itemConf.label });
147114
const badges = await elements[0].getBadgesElement();
148115

149116
expect(elements.length).toEqual(1);
150117

151118
expect(badges.length).toEqual(2);
152119
expect(await badges[0].text()).toEqual(badgesConf[0].label);
153120
expect(await badges[1].text()).toEqual(badgesConf[1].label);
154-
expect(badges[0]).toHaveClassesHarness(badgesConf[0].classes);
155-
expect(badges[1]).toHaveClassesHarness(badgesConf[1].classes);
121+
expect(badges[0]).toHaveClasses(badgesConf[0].classes);
122+
expect(badges[1]).toHaveClasses(badgesConf[1].classes);
156123
});
157124

158125
it('should create 1 header', async () => {
126+
const itemConf = menu[3] as MenuItemHeader;
159127
const elements = await harness.getItemsHeaders();
160128

161129
expect(elements.length).toEqual(1);
162-
expect(await elements[0].text()).toEqual(menu[3].header as string);
130+
expect(await elements[0].text()).toEqual(itemConf.header);
163131
});
164132
});

0 commit comments

Comments
 (0)