import {ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing'; import {RadioButtonComponent, RadioGroupComponent} from './radio-group.component'; import {Component, SimpleChange, SimpleChanges, ViewChild} from '@angular/core'; import {AngularMaterialModule} from '../modules/angular-material.module'; import {MatIconTestingModule} from '@angular/material/icon/testing'; import {FormsModule} from '@angular/forms'; import {By} from '@angular/platform-browser'; @Component({ selector: 'app-test-radio-group-component', template: '' + '' + '' + '' }) class TestRadioGroupComponent { @ViewChild('group') group?: RadioGroupComponent; public test: string | undefined = undefined; public disabled = false; } describe('RadioGroupComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ RadioGroupComponent, RadioButtonComponent, TestRadioGroupComponent ], imports: [AngularMaterialModule, MatIconTestingModule, FormsModule] }) .compileComponents(); }); describe('standalone behaviour', () => { let component: RadioGroupComponent; let fixture: ComponentFixture; beforeEach(() => { fixture = TestBed.createComponent(RadioGroupComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); it('is not dirty upon creation', () => { expect(component.isDirty()).toBe(false); }); it('has value undefined upon creation', () => { expect(component.value).toBe(undefined); }); it('only registers changes if model has structural changes', () => { const onChange = jasmine.createSpy('onChange'); component.writeValue({someProperty: 'a'}); component.registerOnChange(onChange); component.writeValue({someProperty: 'a'}); expect(onChange).toHaveBeenCalledTimes(0); component.writeValue({someProperty: 'b'}); expect(onChange).toHaveBeenCalledTimes(1); }); it('updates inner value when ngModel is updated on the outside', () => { component.writeValue('tis but a test'); expect(component.innerModel).toEqual('tis but a test'); }); it('makes ngModel undefined when set to not relevant', () => { component.writeValue('test'); fixture.detectChanges(); component.notRelevant = true; component.ngOnChanges({notRelevant: {currentValue: true, previousValue: undefined} as SimpleChange} as SimpleChanges); expect(component.value).toBeUndefined(); }); it('re-fills the date when set to not relevant and back again', () => { component.writeValue('test'); fixture.detectChanges(); component.notRelevant = true; component.ngOnChanges({notRelevant: {currentValue: true, previousValue: undefined} as SimpleChange} as SimpleChanges); expect(component.value).toBeUndefined(); component.notRelevant = false; component.ngOnChanges({notRelevant: {currentValue: false, previousValue: true} as SimpleChange} as SimpleChanges); expect(component.value).toBe('test'); }); it('does not register a change on the first value set', () => { // This is to avoid issues where values initialized from an async call will mark the form as dirty const mock = { onChange: (whatever: any) => undefined }; const onChange = spyOn(mock, 'onChange'); component.registerOnChange(onChange); component.writeValue('whatever'); expect(onChange).not.toHaveBeenCalled(); component.writeValue('even more whatever'); expect(onChange).toHaveBeenCalledWith('even more whatever'); }); it('sets value to undefined if value is already selected on click', () => { const clickEvent = new MouseEvent('click'); component.onClick(clickEvent, 'a'); expect(component.value).toBe('a'); component.onClick(clickEvent, 'a'); expect(component.value).toBe(undefined); }); it('updates value if another button is active', () => { const clickEvent = new MouseEvent('click'); component.onClick(clickEvent, 'a'); expect(component.value).toBe('a'); component.onClick(clickEvent, 'b'); expect(component.value).toBe('b'); }); it('does not change value when clicked while disabled', () => { component.setDisabledState(true); const clickEvent = new MouseEvent('click'); component.onClick(clickEvent, 'a'); expect(component.value).toBe(undefined); }); it('is marked as dirty upon first click', () => { const clickEvent = new MouseEvent('click'); component.onClick(clickEvent, 'a'); expect(component.isDirty()).toBe(true); }); }); describe('material integration', () => { let component: TestRadioGroupComponent; let fixture: ComponentFixture; beforeEach(() => { fixture = TestBed.createComponent(TestRadioGroupComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it ('creates a mat-radio-button for each app-radio-button', () => { fixture.detectChanges(); const buttons = fixture.debugElement.queryAll(By.css('mat-radio-button')); expect(buttons.length).toBe(2); }); it ('disables radio-group when disabled', fakeAsync(() => { component.disabled = true; fixture.detectChanges(); tick(); fixture.detectChanges(); const buttons = fixture.debugElement.queryAll(By.css('mat-radio-button')); expect(buttons[0].nativeNode.classList.contains('mat-radio-disabled')).toBeTrue(); expect(buttons[1].nativeNode.classList.contains('mat-radio-disabled')).toBeTrue(); })); it('fills in radio-group based on ngModel value', (done) => { component.test = 'second_test_element'; fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); return fixture.whenStable(); }).then(() => { fixture.detectChanges(); const buttons = fixture.debugElement.queryAll(By.css('mat-radio-button')); expect(buttons[0].nativeNode.classList.contains('mat-radio-checked')).toBeFalse(); expect(buttons[1].nativeNode.classList.contains('mat-radio-checked')).toBeTrue(); done(); }); }); }); });