Skip to content

Commit

Permalink
fix(buttons): fix disabled state for radio button (#5610)
Browse files Browse the repository at this point in the history
Fix disabled state for radio button when form group status is changed to disabled

Close #5140
  • Loading branch information
IraErshova authored Jan 29, 2020
1 parent 14811ae commit d626faf
Show file tree
Hide file tree
Showing 2 changed files with 164 additions and 6 deletions.
21 changes: 20 additions & 1 deletion src/buttons/button-radio-group.directive.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { ChangeDetectorRef, Directive, forwardRef, Provider } from '@angular/core';
import {
ChangeDetectorRef,
ContentChildren,
Directive,
forwardRef,
Provider,
QueryList
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

import { ButtonRadioDirective } from './button-radio.directive';

export const RADIO_CONTROL_VALUE_ACCESSOR: Provider = {
provide: NG_VALUE_ACCESSOR,
/* tslint:disable-next-line: no-use-before-declare */
Expand All @@ -20,6 +29,8 @@ export class ButtonRadioGroupDirective implements ControlValueAccessor {
onChange = Function.prototype;
onTouched = Function.prototype;

@ContentChildren(forwardRef(() => ButtonRadioDirective)) radioButtons: QueryList<ButtonRadioDirective>;

get value() {
return this._value;
}
Expand All @@ -43,4 +54,12 @@ export class ButtonRadioGroupDirective implements ControlValueAccessor {
registerOnTouched(fn: () => {}): void {
this.onTouched = fn;
}

setDisabledState(disabled: boolean): void {
if (this.radioButtons) {
this.radioButtons.forEach(buttons => {
buttons.setDisabledState(disabled);
});
}
}
}
149 changes: 144 additions & 5 deletions src/spec/button.directive.spec.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
// tslint:disable:max-file-line-count no-floating-promises
import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { ComponentFixture, ComponentFixtureAutoDetect, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';

import { ButtonsModule } from '../buttons';

@Component({selector: 'buttons-test', template: ''})
class TestButtonsComponent {
class TestButtonsComponent implements OnInit {
singleModel = '0';
/* tslint:disable-next-line: no-any */
checkModel: any = {left: false, middle: true, right: false};
radioModel = 'Middle';
myForm: FormGroup;

constructor(public cdRef: ChangeDetectorRef) {}
constructor(public cdRef: ChangeDetectorRef,
private formBuilder: FormBuilder) {
}

ngOnInit(): void {
this.myForm = this.formBuilder.group({
radio: 'Middle'
});
}
}

const html = `
Expand Down Expand Up @@ -49,6 +58,16 @@ const html = `
<label class="btn btn-success" [(ngModel)]="radioUncheckableModel" btnRadio="Middle" uncheckable>Middle</label>
<label class="btn btn-success" [(ngModel)]="radioUncheckableModel" btnRadio="Right" uncheckable>Right</label>
</div>
<form [formGroup]="myForm" class="form-inline">
<div class="form-group">
<div class="btn-group reactive-radio" btnRadioGroup formControlName="radio">
<label btnRadio="Left" class="btn btn-primary" tabindex="0" role="button">Left</label>
<label btnRadio="Middle" class="btn btn-primary" tabindex="0" role="button">Middle</label>
<label btnRadio="Right" class="btn btn-primary" tabindex="0" role="button">Right</label>
</div>
</div>
</form>
</div>
`;

Expand Down Expand Up @@ -86,7 +105,7 @@ describe('Directive: Buttons', () => {
fakeAsync(() => {
TestBed.configureTestingModule({
declarations: [TestButtonsComponent],
imports: [ButtonsModule, FormsModule],
imports: [ButtonsModule, FormsModule, ReactiveFormsModule],
providers: [{provide: ComponentFixtureAutoDetect, useValue: true}]
});
})
Expand Down Expand Up @@ -326,6 +345,126 @@ describe('Directive: Buttons', () => {
});

describe('radio', () => {

describe('with reactive form', () => {
let btn = null;

beforeEach(
fakeAsync(() => {
fixture = createComponent(html);
context = fixture.componentInstance;
element = fixture.nativeElement;

fixture.detectChanges();
tick();
fixture.detectChanges();

btn = element.querySelector('.btn-group.reactive-radio');
})
);

it(
'should set active class based on model',
fakeAsync(() => {
expect(btn.children[0].classList).not.toContain('active');
expect(btn.children[1].classList).toContain('active');
expect(btn.children[2].classList).not.toContain('active');

context.myForm.get('radio').setValue('Left');
fixture.detectChanges();
tick();

expect(btn.children[0].classList).toContain('active');
expect(btn.children[1].classList).not.toContain('active');
expect(btn.children[2].classList).not.toContain('active');
})
);

it(
'should set active class via click',
fakeAsync(() => {
(btn.children[0] as HTMLElement).click();
fixture.detectChanges();
tick();

expect(context.myForm.get('radio').value).toEqual('Left');
expect(btn.children[0].classList).toContain('active');
expect(btn.children[1].classList).not.toContain('active');
expect(btn.children[2].classList).not.toContain('active');

(btn.children[2] as HTMLElement).click();
fixture.detectChanges();
tick();

expect(context.myForm.get('radio').value).toEqual('Right');
expect(btn.children[0].classList).not.toContain('active');
expect(btn.children[1].classList).not.toContain('active');
expect(btn.children[2].classList).toContain('active');
})
);

it(
'should do nothing when clicking an active radio',
fakeAsync(() => {
context.myForm.get('radio').setValue('Left');
fixture.detectChanges();
tick();

expect(btn.children[0].classList).toContain('active');
expect(btn.children[1].classList).not.toContain('active');
expect(btn.children[2].classList).not.toContain('active');

(btn.children[0] as HTMLElement).click();
fixture.detectChanges();

expect(context.myForm.get('radio').value).toEqual('Left');
expect(btn.children[0].classList).toContain('active');
expect(btn.children[1].classList).not.toContain('active');
expect(btn.children[2].classList).not.toContain('active');
})
);

it(
'should set disabled attribute when form status is changed to disabled',
fakeAsync(() => {
expect(btn.children[0].getAttribute('disabled')).toBeNull();
expect(btn.children[1].getAttribute('disabled')).toBeNull();
expect(btn.children[2].getAttribute('disabled')).toBeNull();

context.myForm.disable();
fixture.detectChanges();
tick();

expect(btn.children[0].getAttribute('disabled')).toEqual('disabled');
expect(btn.children[1].getAttribute('disabled')).toEqual('disabled');
expect(btn.children[2].getAttribute('disabled')).toEqual('disabled');
})
);

it(
'should not change model when form is disabled',
fakeAsync(() => {
context.myForm.get('radio').setValue('Left');
fixture.detectChanges();
tick();

expect(btn.children[0].classList).toContain('active');
expect(btn.children[1].classList).not.toContain('active');
expect(btn.children[2].classList).not.toContain('active');

context.myForm.disable();
(btn.children[1] as HTMLElement).click();
fixture.detectChanges();
tick();

expect(context.myForm.get('radio').value).toEqual('Left');
expect(btn.children[0].classList).toContain('active');
expect(btn.children[1].classList).not.toContain('active');
expect(btn.children[2].classList).not.toContain('active');
})
);
});

it(
'should set active class based on model',
fakeAsync(() => {
Expand Down

0 comments on commit d626faf

Please sign in to comment.