import {dispatchFakeEvent} from '@angular/cdk/testing/private'; import { ApplicationRef, Component, DebugElement, EnvironmentProviders, Provider, Type, } from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {MAT_PROGRESS_BAR_DEFAULT_OPTIONS, MatProgressBarModule} from './index'; import {MatProgressBar} from './progress-bar'; describe('MatProgressBar', () => { function createComponent( componentType: Type, providers: (Provider | EnvironmentProviders)[] = [], ): ComponentFixture { TestBed.configureTestingModule({ imports: [MatProgressBarModule, componentType], providers, }); return TestBed.createComponent(componentType); } // All children need to be hidden for screen readers in order to support ChromeVox. // More context in the issue: https://github.com/angular/components/issues/22165. it('should have elements wrapped in aria-hidden div', () => { const fixture = createComponent(BasicProgressBar); const host = fixture.nativeElement as Element; const element = host.children[0]; const children = element.children; expect(children.length).toBe(3); const ariaHidden = Array.from(children).map(child => child.getAttribute('aria-hidden')); expect(ariaHidden).toEqual(['true', 'true', 'true']); }); describe('with animation', () => { describe('basic progress-bar', () => { it('should apply a mode of "determinate" if no mode is provided.', () => { const fixture = createComponent(BasicProgressBar); fixture.detectChanges(); const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'))!; expect(progressElement.componentInstance.mode).toBe('determinate'); }); it('should define default values for value and bufferValue attributes', () => { const fixture = createComponent(BasicProgressBar); fixture.detectChanges(); const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'))!; expect(progressElement.componentInstance.value).toBe(0); expect(progressElement.componentInstance.bufferValue).toBe(0); }); it('should clamp value and bufferValue between 0 and 100', () => { const fixture = createComponent(BasicProgressBar); fixture.detectChanges(); const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'))!; const progressComponent = progressElement.componentInstance; progressComponent.value = 50; expect(progressComponent.value).toBe(50); progressComponent.value = 999; expect(progressComponent.value).toBe(100); progressComponent.value = -10; expect(progressComponent.value).toBe(0); progressComponent.bufferValue = -29; expect(progressComponent.bufferValue).toBe(0); progressComponent.bufferValue = 9; expect(progressComponent.bufferValue).toBe(9); progressComponent.bufferValue = 1320; expect(progressComponent.bufferValue).toBe(100); }); it('should set the proper transform based on the current value', () => { const fixture = createComponent(BasicProgressBar); fixture.detectChanges(); const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'))!; const progressComponent = progressElement.componentInstance; const primaryStyles = progressElement.nativeElement.querySelector( '.mdc-linear-progress__primary-bar', ).style; const bufferStyles = progressElement.nativeElement.querySelector( '.mdc-linear-progress__buffer-bar', ).style; // Parse out and round the value since different // browsers return the value with a different precision. const getBufferValue = () => Math.floor(parseInt(bufferStyles.flexBasis)); expect(primaryStyles.transform).toBe('scaleX(0)'); expect(getBufferValue()).toBe(100); progressComponent.value = 40; fixture.detectChanges(); expect(primaryStyles.transform).toBe('scaleX(0.4)'); expect(getBufferValue()).toBe(100); progressComponent.value = 35; progressComponent.bufferValue = 55; fixture.detectChanges(); expect(primaryStyles.transform).toBe('scaleX(0.35)'); expect(getBufferValue()).toBe(100); progressComponent.mode = 'buffer'; fixture.detectChanges(); expect(primaryStyles.transform).toBe('scaleX(0.35)'); expect(getBufferValue()).toEqual(55); progressComponent.value = 60; progressComponent.bufferValue = 60; fixture.detectChanges(); expect(primaryStyles.transform).toBe('scaleX(0.6)'); expect(getBufferValue()).toEqual(60); }); it('should remove the `aria-valuenow` attribute in indeterminate mode', () => { const fixture = createComponent(BasicProgressBar); fixture.detectChanges(); const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'))!; const progressComponent = progressElement.componentInstance; progressComponent.mode = 'determinate'; progressComponent.value = 50; fixture.detectChanges(); expect(progressElement.nativeElement.getAttribute('aria-valuenow')) .withContext('Expected aria-valuenow to be set in determinate mode.') .toBe('50'); progressComponent.mode = 'indeterminate'; fixture.detectChanges(); expect(progressElement.nativeElement.hasAttribute('aria-valuenow')) .withContext('Expect aria-valuenow to be cleared in indeterminate mode.') .toBe(false); }); it('should remove the `aria-valuenow` attribute in query mode', () => { const fixture = createComponent(BasicProgressBar); fixture.detectChanges(); const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'))!; const progressComponent = progressElement.componentInstance; progressComponent.mode = 'determinate'; progressComponent.value = 50; fixture.detectChanges(); expect(progressElement.nativeElement.getAttribute('aria-valuenow')) .withContext('Expected aria-valuenow to be set in determinate mode.') .toBe('50'); progressComponent.mode = 'query'; fixture.detectChanges(); expect(progressElement.nativeElement.hasAttribute('aria-valuenow')) .withContext('Expect aria-valuenow to be cleared in query mode.') .toBe(false); }); it('should be able to configure the default progress bar options via DI', () => { const fixture = createComponent(BasicProgressBar, [ { provide: MAT_PROGRESS_BAR_DEFAULT_OPTIONS, useValue: { mode: 'buffer', color: 'warn', }, }, ]); fixture.detectChanges(); const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'))!; expect(progressElement.componentInstance.mode).toBe('buffer'); expect(progressElement.componentInstance.color).toBe('warn'); }); it('should update the DOM transform when the value has changed', () => { const fixture = createComponent(BasicProgressBar); fixture.detectChanges(); const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'))!; const progressComponent = progressElement.componentInstance; const primaryBar = progressElement.nativeElement.querySelector( '.mdc-linear-progress__primary-bar', ); expect(primaryBar.style.transform).toBe('scaleX(0)'); progressComponent.value = 40; fixture.detectChanges(); expect(primaryBar.style.transform).toBe('scaleX(0.4)'); }); it('should update the DOM transform when the bufferValue has changed', () => { const fixture = createComponent(BasicProgressBar); fixture.detectChanges(); const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'))!; const progressComponent = progressElement.componentInstance; const bufferBar = progressElement.nativeElement.querySelector( '.mdc-linear-progress__buffer-bar', ); progressComponent.mode = 'buffer'; fixture.detectChanges(); expect(bufferBar.style.flexBasis).toBe('0%'); progressComponent.bufferValue = 40; fixture.detectChanges(); expect(bufferBar.style.flexBasis).toBe('40%'); }); }); describe('animation trigger on determinate setting', () => { let fixture: ComponentFixture; let progressComponent: MatProgressBar; let primaryValueBar: DebugElement; beforeEach(() => { fixture = createComponent(BasicProgressBar); const progressElement = fixture.debugElement.query(By.css('mat-progress-bar'))!; progressComponent = progressElement.componentInstance; primaryValueBar = progressElement.query(By.css('.mdc-linear-progress__primary-bar'))!; }); it('should trigger output event on primary value bar animation end', () => { fixture.detectChanges(); const animationEndSpy = jasmine.createSpy(); progressComponent.animationEnd.subscribe(animationEndSpy); progressComponent.value = 40; expect(animationEndSpy).not.toHaveBeenCalled(); // On animation end, output should be emitted. dispatchFakeEvent(primaryValueBar.nativeElement, 'transitionend', true); expect(animationEndSpy).toHaveBeenCalledWith({value: 40}); }); }); describe('animation trigger on buffer setting', () => { let fixture: ComponentFixture; let progressElement: DebugElement; let progressComponent: MatProgressBar; let primaryValueBar: DebugElement; beforeEach(() => { fixture = createComponent(BufferProgressBar); progressElement = fixture.debugElement.query(By.css('mat-progress-bar'))!; progressComponent = progressElement.componentInstance; primaryValueBar = progressElement.query(By.css('.mdc-linear-progress__primary-bar'))!; }); it('should bind on transitionend eventListener on primaryBarValue', () => { spyOn(progressElement.nativeElement, 'addEventListener'); fixture.detectChanges(); expect(progressElement.nativeElement.addEventListener).toHaveBeenCalled(); expect(progressElement.nativeElement.addEventListener.calls.mostRecent().args[0]).toBe( 'transitionend', ); }); it('should trigger output event on primary value bar animation end', () => { fixture.detectChanges(); const animationEndSpy = jasmine.createSpy(); progressComponent.animationEnd.subscribe(animationEndSpy); progressComponent.value = 40; expect(animationEndSpy).not.toHaveBeenCalled(); // On animation end, output should be emitted. dispatchFakeEvent(primaryValueBar.nativeElement, 'transitionend', true); expect(animationEndSpy).toHaveBeenCalledWith({value: 40}); }); it('should trigger output event with value not bufferValue', () => { fixture.detectChanges(); const animationEndSpy = jasmine.createSpy(); progressComponent.animationEnd.subscribe(animationEndSpy); progressComponent.value = 40; progressComponent.bufferValue = 70; expect(animationEndSpy).not.toHaveBeenCalled(); // On animation end, output should be emitted. dispatchFakeEvent(primaryValueBar.nativeElement, 'transitionend', true); expect(animationEndSpy).toHaveBeenCalledWith({value: 40}); }); it('should not run change detection if there are no `animationEnd` observers', () => { fixture.detectChanges(); const animationEndSpy = jasmine.createSpy(); const appRef = TestBed.inject(ApplicationRef); spyOn(appRef, 'tick'); progressComponent.value = 30; progressComponent.bufferValue = 60; // On animation end, output should be emitted. dispatchFakeEvent(primaryValueBar.nativeElement, 'transitionend', true); expect(appRef.tick).not.toHaveBeenCalled(); progressComponent.animationEnd.subscribe(animationEndSpy); progressComponent.value = 40; progressComponent.bufferValue = 70; // On animation end, output should be emitted. dispatchFakeEvent(primaryValueBar.nativeElement, 'transitionend', true); expect(animationEndSpy).toHaveBeenCalledWith({value: 40}); }); }); }); }); @Component({ template: '', standalone: true, imports: [MatProgressBar], }) class BasicProgressBar {} @Component({ template: '', standalone: true, imports: [MatProgressBar], }) class BufferProgressBar {}