3
votes

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?

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.AliF50
Yes I do have them in SharedMaterialModuleLempkin
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.Bargros

3 Answers

1
votes

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);
  });

1
votes

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"]`,
  }),
);
0
votes

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.