Unit test mat-menu with Karma-Jasmine
Asked Answered
C

3

5

I have a mat-menu in which content may differ regarding user. I try to write unit test but from what I see, jasmine doesn't see the CDK div, so I cannot grab menu entries.

My template:

<button id="account" mat-icon-button [matMenuTriggerFor]="menu" aria-label="Profile">
    <mat-icon>person</mat-icon>
</button>
<mat-menu #menu="matMenu">
    <button mat-menu-item *ngxPermissionsOnly="PERMISSION.USER_LIST" id="user-list" (click)="usersList()">
        <mat-icon>recent_actors</mat-icon>
    </button>
    <button mat-menu-item *ngxPermissionsOnly="PERMISSION.INFORMATIONS" id="informations" (click)="infoList()">
        <mat-icon>info</mat-icon>
    </button>
    <button mat-menu-item id="logout" (click)="logout()">
        <mat-icon>exit_to_app</mat-icon>
    </button>
</mat-menu>

The unit test:

let component: HeaderComponent;
let fixture: ComponentFixture < HeaderComponent > ;

const providers: any[] = headerProviders;

beforeEach(async (() => {
    TestBed.configureTestingModule({
            declarations: [
                HeaderComponent,
                NgxPermissionsRestrictStubDirective
            ],
            providers: providers,
            imports: [
                BrowserAnimationsModule,
                BrowserModule,
                CommonModule,
                CommonSogetrelModule,
                FlexLayoutModule,
                SharedMaterialModule,
                RouterTestingModule.withRoutes([])
            ]
        })
        .compileComponents()
        .then(() => {
            fixture = TestBed.createComponent(HeaderComponent);
            component = fixture.componentInstance;
        });
}));

it('should not display elements which needs permissions', () => {
    fixture.nativeElement.querySelector('#account').click();
    fixture.detectChanges();
    expect(logoutBtn).toBeTruthy('Le bouton Déconnexion doit être affiché');
    expect(fixture.debugElement.nativeElement.querySelector('#user-list')).toBeFalsy();

});

I've tried with

console.info(fixture.nativeElement.parentNode);
const menu = fixture.nativeElement.parentNode.querySelector('.mat-menu-panel');
expect(menu).toBeTruthy();

What I can see with the console.info is that there's no CDK div on the page, and so obviously the .mat-menu-panel isn't found.

Any idea about how to test the mat-menu content?

Calder answered 28/4, 2020 at 14:32 Comment(3)
Make sure you have MatMenuModule and MatIconModule in your imports array. You most likely have it since you are not getting errors for compiling.Peeples
Yes I do have them in SharedMaterialModuleCalder
have you tried fixture.debugElement.query(By.css('#account')).nativeElement.click() and expect(fixture.debugElement.query(By.css('#logout'))).toBeTruthy(), expect(fixture.debugElement.query(By.css('#user-list'))).toBeFalsy() ?, still I think you should add whatever the console output is, or the output of the test, actually both.Baez
V
5

MatMenuHarness is the proper way to test the mat-menu but it has a tiny unintuitive detail about it. Given:

<div class="view-contract__portal-header">
  <h2>Counterparties</h2>
  <button
    mat-stroked-button
    color="primary"
    data-testid="add-counterparty"
    [matMenuTriggerFor]="menu"
  >
    <mat-icon>add</mat-icon> Add counterparty
  </button>
</div>

<mat-menu #menu="matMenu" data-testid="add-counterparty-menu">
  <button
    *ngFor="let role of roles"
    mat-menu-item
    [attr.data-testid]="'add-counterparty-' + role"
    (click)="addCounterparty(role)"
  >
    {{ role }}
  </button>
</mat-menu>

Then MatMenuHarness should be instantiated by looking for the element to which the matMenuTriggerFor attribute is applied and NOT the actual mat-menu element.

addCounterpartyMenu = await loader.getHarness<MatMenuHarness>(
  MatMenuHarness.with({
    selector: `[data-testid="add-counterparty"]`,
  }),
);
Viscountcy answered 7/2, 2021 at 20:38 Comment(2)
I keep getting: Error: Failed to find element matching one of the following queries: (MatMenuHarness with host element matching selector: ".mat-menu-trigger" satisfying the constraints: host matches selector "#emailMenu") even tried calling it without "with" filters. Still no Harnesses returned.Coadjutress
@Coadjutress been long time since I coded in Angular, but maybe your element is hiddenViscountcy
B
3

I think you should try it with the MatMenuHarness.

https://material.angular.io/components/menu/api#MatMenuHarness

it('should not display elements which needs permissions', async () => {
    let loader: HarnessLoader = TestbedHarnessEnvironment.loader(fixture);
    fixture.detectChanges();
    let menu = await testHarness.loader.getHarness(MatMenuHarness);
    let items = await menu.getItems();
    expect(items.length).toBe(1);
  });

Bartels answered 6/2, 2021 at 22:46 Comment(0)
H
0

I think the issue is you are doing all in the same clock interaction, because of that, the DOM will not be updated until the next clock.

To handle this you can use fakeAysnc and tick of Angular.

I think we can rewrite your test with fakeAsync like this:

    it('should not display elements which needs permissions', fakeAsync(() => {
        fixture.nativeElement.querySelector('#account').click();
        tick();
        fixture.detectChanges();
        expect(logoutBtn).toBeTruthy('Le bouton Déconnexion doit être affiché');
        expect(fixture.debugElement.nativeElement.querySelector('#user-list')).toBeFalsy();
      }));

I think it should work with that.

Horsemint answered 29/4, 2020 at 13:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.