import {waitForAsync, ComponentFixture, TestBed} from '@angular/core/testing'; import {Component, DebugElement, signal} from '@angular/core'; import {By} from '@angular/platform-browser'; import { dispatchFakeEvent, dispatchKeyboardEvent, createKeyboardEvent, dispatchEvent, } from '@angular/cdk/testing/private'; import {SPACE, ENTER} from '@angular/cdk/keycodes'; import {MatOption, MatOptionModule, MAT_OPTION_PARENT_COMPONENT} from './index'; describe('MatOption component', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [MatOptionModule, BasicOption], }); })); it('should complete the `stateChanges` stream on destroy', () => { const fixture = TestBed.createComponent(BasicOption); fixture.detectChanges(); const optionInstance: MatOption = fixture.debugElement.query( By.directive(MatOption), )!.componentInstance; const completeSpy = jasmine.createSpy('complete spy'); const subscription = optionInstance._stateChanges.subscribe({complete: completeSpy}); fixture.destroy(); expect(completeSpy).toHaveBeenCalled(); subscription.unsubscribe(); }); it('should not emit to `onSelectionChange` if selecting an already-selected option', () => { const fixture = TestBed.createComponent(BasicOption); fixture.detectChanges(); const optionInstance: MatOption = fixture.debugElement.query( By.directive(MatOption), )!.componentInstance; optionInstance.select(); expect(optionInstance.selected).toBe(true); const spy = jasmine.createSpy('selection change spy'); const subscription = optionInstance.onSelectionChange.subscribe(spy); optionInstance.select(); fixture.detectChanges(); expect(optionInstance.selected).toBe(true); expect(spy).not.toHaveBeenCalled(); subscription.unsubscribe(); }); it('should not emit to `onSelectionChange` if deselecting an unselected option', () => { const fixture = TestBed.createComponent(BasicOption); fixture.detectChanges(); const optionInstance: MatOption = fixture.debugElement.query( By.directive(MatOption), )!.componentInstance; optionInstance.deselect(); expect(optionInstance.selected).toBe(false); const spy = jasmine.createSpy('selection change spy'); const subscription = optionInstance.onSelectionChange.subscribe(spy); optionInstance.deselect(); fixture.detectChanges(); expect(optionInstance.selected).toBe(false); expect(spy).not.toHaveBeenCalled(); subscription.unsubscribe(); }); it('should be able to set a custom id', () => { const fixture = TestBed.createComponent(BasicOption); fixture.componentInstance.id.set('custom-option'); fixture.detectChanges(); const optionInstance = fixture.debugElement.query(By.directive(MatOption))!.componentInstance; expect(optionInstance.id).toBe('custom-option'); }); it('should select the option when pressing space', () => { const fixture = TestBed.createComponent(BasicOption); fixture.detectChanges(); const optionDebugElement = fixture.debugElement.query(By.directive(MatOption))!; const optionNativeElement: HTMLElement = optionDebugElement.nativeElement; const optionInstance: MatOption = optionDebugElement.componentInstance; const spy = jasmine.createSpy('selection change spy'); const subscription = optionInstance.onSelectionChange.subscribe(spy); const event = dispatchKeyboardEvent(optionNativeElement, 'keydown', SPACE); fixture.detectChanges(); expect(spy).toHaveBeenCalled(); expect(event.defaultPrevented).toBe(true); subscription.unsubscribe(); }); it('should select the option when pressing enter', () => { const fixture = TestBed.createComponent(BasicOption); fixture.detectChanges(); const optionDebugElement = fixture.debugElement.query(By.directive(MatOption))!; const optionNativeElement: HTMLElement = optionDebugElement.nativeElement; const optionInstance: MatOption = optionDebugElement.componentInstance; const spy = jasmine.createSpy('selection change spy'); const subscription = optionInstance.onSelectionChange.subscribe(spy); const event = dispatchKeyboardEvent(optionNativeElement, 'keydown', ENTER); fixture.detectChanges(); expect(spy).toHaveBeenCalled(); expect(event.defaultPrevented).toBe(true); subscription.unsubscribe(); }); it('should not do anything when pressing the selection keys with a modifier', () => { const fixture = TestBed.createComponent(BasicOption); fixture.detectChanges(); const optionDebugElement = fixture.debugElement.query(By.directive(MatOption))!; const optionNativeElement: HTMLElement = optionDebugElement.nativeElement; const optionInstance: MatOption = optionDebugElement.componentInstance; const spy = jasmine.createSpy('selection change spy'); const subscription = optionInstance.onSelectionChange.subscribe(spy); [ENTER, SPACE].forEach(key => { const event = createKeyboardEvent('keydown', key); Object.defineProperty(event, 'shiftKey', {get: () => true}); dispatchEvent(optionNativeElement, event); fixture.detectChanges(); expect(event.defaultPrevented).toBe(false); }); expect(spy).not.toHaveBeenCalled(); subscription.unsubscribe(); }); describe('ripples', () => { let fixture: ComponentFixture; let optionDebugElement: DebugElement; let optionNativeElement: HTMLElement; let optionInstance: MatOption; beforeEach(() => { fixture = TestBed.createComponent(BasicOption); fixture.detectChanges(); optionDebugElement = fixture.debugElement.query(By.directive(MatOption))!; optionNativeElement = optionDebugElement.nativeElement; optionInstance = optionDebugElement.componentInstance; }); it('should show ripples by default', () => { expect(optionInstance.disableRipple) .withContext('Expected ripples to be enabled by default') .toBeFalsy(); expect(optionNativeElement.querySelectorAll('.mat-ripple-element').length) .withContext('Expected no ripples to show up initially') .toBe(0); dispatchFakeEvent(optionNativeElement, 'mousedown'); dispatchFakeEvent(optionNativeElement, 'mouseup'); expect(optionNativeElement.querySelectorAll('.mat-ripple-element').length) .withContext('Expected one ripple to show up after a fake click.') .toBe(1); }); it('should not show ripples if the option is disabled', () => { expect(optionNativeElement.querySelectorAll('.mat-ripple-element').length) .withContext('Expected no ripples to show up initially') .toBe(0); fixture.componentInstance.disabled.set(true); fixture.detectChanges(); dispatchFakeEvent(optionNativeElement, 'mousedown'); dispatchFakeEvent(optionNativeElement, 'mouseup'); expect(optionNativeElement.querySelectorAll('.mat-ripple-element').length) .withContext('Expected no ripples to show up after click on a disabled option.') .toBe(0); }); }); it('should have a focus indicator', () => { const fixture = TestBed.createComponent(BasicOption); const optionNativeElement = fixture.debugElement.query(By.directive(MatOption))!.nativeElement; expect(optionNativeElement.parentElement.querySelector('.mat-focus-indicator')) .withContext( 'expected to find a focus indicator on ' + "either the mat-option element or one of it's children", ) .not.toBeNull(); }); describe('inside inert group', () => { let fixture: ComponentFixture; beforeEach(waitForAsync(() => { TestBed.resetTestingModule(); TestBed.configureTestingModule({ imports: [MatOptionModule, InsideGroup], providers: [ { provide: MAT_OPTION_PARENT_COMPONENT, useValue: {inertGroups: true}, }, ], }); fixture = TestBed.createComponent(InsideGroup); fixture.detectChanges(); })); it('should remove all accessibility-related attributes from the group', () => { const group: HTMLElement = fixture.nativeElement.querySelector('mat-optgroup'); expect(group.hasAttribute('role')).toBe(false); expect(group.hasAttribute('aria-disabled')).toBe(false); expect(group.hasAttribute('aria-labelledby')).toBe(false); }); it('should mirror the group label inside the option', () => { const option: HTMLElement = fixture.nativeElement.querySelector('mat-option'); expect(option.textContent?.trim()).toBe('Option(Group)'); }); }); }); @Component({ template: ``, standalone: true, imports: [MatOptionModule], }) class BasicOption { disabled = signal(false); id = signal(''); } @Component({ template: ` Option `, standalone: true, imports: [MatOptionModule], }) class InsideGroup {}