Skip to content

Commit 2a27883

Browse files
committed
feat(button-toggle): general component cleanup and support value input in multiple mode
* Reworks the `MatButtonToggleGroup` component to remove the need for the `MatButtonToggleGroupMultiple` component and to avoid having to implement features in two places. * Reworks the `MatButtonToggleGroup` to use the `SelectionModel` for managing its state. * Switches all of the `async` button toggle tests to run in `fakeAsync`. As a side-effect of the above-mentioned changes, the toggle group in multiple mode now supports the same inputs as the one in single-selection mode (`value` input/output, `name` input, `change` event etc.). Note: this is along the same lines as #2773, however #2773 got left behind for too long and it would need a lot more work to fit in our current setup. Fixes #9058.
1 parent c3d7cd9 commit 2a27883

File tree

3 files changed

+237
-245
lines changed

3 files changed

+237
-245
lines changed

src/lib/button-toggle/button-toggle-module.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,14 @@
77
*/
88

99
import {NgModule} from '@angular/core';
10-
import {MatButtonToggleGroup, MatButtonToggleGroupMultiple, MatButtonToggle} from './button-toggle';
10+
import {MatButtonToggleGroup, MatButtonToggle} from './button-toggle';
1111
import {MatCommonModule} from '@angular/material/core';
12-
import {UNIQUE_SELECTION_DISPATCHER_PROVIDER} from '@angular/cdk/collections';
1312
import {A11yModule} from '@angular/cdk/a11y';
1413

1514

1615
@NgModule({
1716
imports: [MatCommonModule, A11yModule],
18-
exports: [
19-
MatButtonToggleGroup,
20-
MatButtonToggleGroupMultiple,
21-
MatButtonToggle,
22-
MatCommonModule,
23-
],
24-
declarations: [MatButtonToggleGroup, MatButtonToggleGroupMultiple, MatButtonToggle],
25-
providers: [UNIQUE_SELECTION_DISPATCHER_PROVIDER]
17+
exports: [MatCommonModule, MatButtonToggleGroup, MatButtonToggle],
18+
declarations: [MatButtonToggleGroup, MatButtonToggle],
2619
})
2720
export class MatButtonToggleModule {}

src/lib/button-toggle/button-toggle.spec.ts

Lines changed: 51 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,18 @@
1-
import {
2-
async,
3-
fakeAsync,
4-
tick,
5-
ComponentFixture,
6-
TestBed,
7-
} from '@angular/core/testing';
1+
import {fakeAsync, tick, ComponentFixture, TestBed} from '@angular/core/testing';
82
import {NgModel, FormsModule, ReactiveFormsModule, FormControl} from '@angular/forms';
93
import {Component, DebugElement} from '@angular/core';
104
import {By} from '@angular/platform-browser';
115
import {
126
MatButtonToggleGroup,
13-
MatButtonToggle,
147
MatButtonToggleGroupMultiple,
8+
MatButtonToggle,
159
MatButtonToggleChange,
1610
MatButtonToggleModule,
1711
} from './index';
1812

19-
2013
describe('MatButtonToggle with forms', () => {
2114

22-
beforeEach(async(() => {
15+
beforeEach(fakeAsync(() => {
2316
TestBed.configureTestingModule({
2417
imports: [MatButtonToggleModule, FormsModule, ReactiveFormsModule],
2518
declarations: [
@@ -37,7 +30,7 @@ describe('MatButtonToggle with forms', () => {
3730
let groupInstance: MatButtonToggleGroup;
3831
let testComponent: ButtonToggleGroupWithFormControl;
3932

40-
beforeEach(async(() => {
33+
beforeEach(fakeAsync(() => {
4134
fixture = TestBed.createComponent(ButtonToggleGroupWithFormControl);
4235
fixture.detectChanges();
4336

@@ -89,7 +82,7 @@ describe('MatButtonToggle with forms', () => {
8982
let groupNgModel: NgModel;
9083
let buttonToggleLabels: HTMLElement[];
9184

92-
beforeEach(async(() => {
85+
beforeEach(fakeAsync(() => {
9386
fixture = TestBed.createComponent(ButtonToggleGroupWithNgModel);
9487
fixture.detectChanges();
9588
testComponent = fixture.debugElement.componentInstance;
@@ -143,7 +136,10 @@ describe('MatButtonToggle with forms', () => {
143136
for (let buttonToggle of buttonToggleInstances) {
144137
expect(buttonToggle.checked).toBe(groupInstance.value === buttonToggle.value);
145138
}
146-
expect(groupInstance.selected!.value).toBe(groupInstance.value);
139+
140+
const selected = groupInstance.selected as MatButtonToggle;
141+
142+
expect(selected.value).toBe(groupInstance.value);
147143
});
148144

149145
it('should have the correct FormControl state initially and after interaction',
@@ -183,7 +179,7 @@ describe('MatButtonToggle with forms', () => {
183179

184180
describe('MatButtonToggle without forms', () => {
185181

186-
beforeEach(async(() => {
182+
beforeEach(fakeAsync(() => {
187183
TestBed.configureTestingModule({
188184
imports: [MatButtonToggleModule],
189185
declarations: [
@@ -318,7 +314,6 @@ describe('MatButtonToggle without forms', () => {
318314
let changeSpy = jasmine.createSpy('button-toggle change listener');
319315
buttonToggleInstances[0].change.subscribe(changeSpy);
320316

321-
322317
buttonToggleLabelElements[0].click();
323318
fixture.detectChanges();
324319
tick();
@@ -392,14 +387,16 @@ describe('MatButtonToggle without forms', () => {
392387

393388
fixture.detectChanges();
394389

390+
// Note that we cast to a boolean, because the event has some circular references
391+
// which will crash the runner when Jasmine attempts to stringify them.
392+
expect(!!testComponent.lastEvent).toBe(false);
395393
expect(groupInstance.value).toBe('red');
396-
expect(testComponent.lastEvent).toBeFalsy();
397394

398395
groupInstance.value = 'green';
399396
fixture.detectChanges();
400397

398+
expect(!!testComponent.lastEvent).toBe(false);
401399
expect(groupInstance.value).toBe('green');
402-
expect(testComponent.lastEvent).toBeFalsy();
403400
});
404401

405402
});
@@ -411,20 +408,19 @@ describe('MatButtonToggle without forms', () => {
411408
let buttonToggleDebugElements: DebugElement[];
412409
let buttonToggleNativeElements: HTMLElement[];
413410
let buttonToggleLabelElements: HTMLLabelElement[];
414-
let groupInstance: MatButtonToggleGroupMultiple;
411+
let groupInstance: MatButtonToggleGroup;
415412
let buttonToggleInstances: MatButtonToggle[];
416413
let testComponent: ButtonTogglesInsideButtonToggleGroupMultiple;
417414

418-
beforeEach(async(() => {
415+
beforeEach(fakeAsync(() => {
419416
fixture = TestBed.createComponent(ButtonTogglesInsideButtonToggleGroupMultiple);
420417
fixture.detectChanges();
421418

422419
testComponent = fixture.debugElement.componentInstance;
423420

424-
groupDebugElement = fixture.debugElement.query(By.directive(MatButtonToggleGroupMultiple));
421+
groupDebugElement = fixture.debugElement.query(By.directive(MatButtonToggleGroup));
425422
groupNativeElement = groupDebugElement.nativeElement;
426-
groupInstance = groupDebugElement.injector.get<MatButtonToggleGroupMultiple>(
427-
MatButtonToggleGroupMultiple);
423+
groupInstance = groupDebugElement.injector.get<MatButtonToggleGroup>(MatButtonToggleGroup);
428424

429425
buttonToggleDebugElements = fixture.debugElement.queryAll(By.directive(MatButtonToggle));
430426
buttonToggleNativeElements = buttonToggleDebugElements
@@ -448,16 +444,22 @@ describe('MatButtonToggle without forms', () => {
448444
let nativeCheckboxLabel = buttonToggleDebugElements[0].query(By.css('label')).nativeElement;
449445

450446
nativeCheckboxLabel.click();
447+
448+
expect(groupInstance.value).toEqual(['eggs']);
451449
expect(buttonToggleInstances[0].checked).toBe(true);
452450
});
453451

454452
it('should allow for multiple toggles to be selected', () => {
455453
buttonToggleInstances[0].checked = true;
456454
fixture.detectChanges();
455+
456+
expect(groupInstance.value).toEqual(['eggs']);
457457
expect(buttonToggleInstances[0].checked).toBe(true);
458458

459459
buttonToggleInstances[1].checked = true;
460460
fixture.detectChanges();
461+
462+
expect(groupInstance.value).toEqual(['eggs', 'flour']);
461463
expect(buttonToggleInstances[1].checked).toBe(true);
462464
expect(buttonToggleInstances[0].checked).toBe(true);
463465
});
@@ -468,6 +470,7 @@ describe('MatButtonToggle without forms', () => {
468470
nativeCheckboxInput.click();
469471
fixture.detectChanges();
470472

473+
expect(groupInstance.value).toEqual(['eggs']);
471474
expect(buttonToggleInstances[0].checked).toBe(true);
472475
});
473476

@@ -480,18 +483,23 @@ describe('MatButtonToggle without forms', () => {
480483
expect(groupNativeElement.classList).toContain('mat-button-toggle-vertical');
481484
});
482485

483-
it('should deselect a button toggle when selected twice', () => {
484-
buttonToggleNativeElements[0].click();
486+
it('should deselect a button toggle when selected twice', fakeAsync(() => {
487+
buttonToggleLabelElements[0].click();
485488
fixture.detectChanges();
489+
tick();
486490

487-
buttonToggleNativeElements[0].click();
491+
expect(buttonToggleInstances[0].checked).toBe(true);
492+
expect(groupInstance.value).toEqual(['eggs']);
493+
494+
buttonToggleLabelElements[0].click();
488495
fixture.detectChanges();
496+
tick();
489497

498+
expect(groupInstance.value).toEqual([]);
490499
expect(buttonToggleInstances[0].checked).toBe(false);
491-
});
500+
}));
492501

493502
it('should emit a change event for state changes', fakeAsync(() => {
494-
495503
expect(buttonToggleInstances[0].checked).toBe(false);
496504

497505
let changeSpy = jasmine.createSpy('button-toggle change listener');
@@ -501,17 +509,29 @@ describe('MatButtonToggle without forms', () => {
501509
fixture.detectChanges();
502510
tick();
503511
expect(changeSpy).toHaveBeenCalled();
512+
expect(groupInstance.value).toEqual(['eggs']);
504513

505514
buttonToggleLabelElements[0].click();
506515
fixture.detectChanges();
507516
tick();
517+
expect(groupInstance.value).toEqual([]);
508518

509519
// The default browser behavior is to emit an event, when the value was set
510520
// to false. That's because the current input type is set to `checkbox` when
511521
// using the multiple mode.
512522
expect(changeSpy).toHaveBeenCalledTimes(2);
513523
}));
514524

525+
it('should throw when attempting to assign a non-array value', () => {
526+
expect(() => {
527+
groupInstance.value = 'not-an-array';
528+
}).toThrowError(/Value must be an array/);
529+
});
530+
531+
it('should be able to query for the deprecated `MatButtonToggleGroupMultiple`', () => {
532+
expect(fixture.debugElement.query(By.directive(MatButtonToggleGroupMultiple))).toBeTruthy();
533+
});
534+
515535
});
516536

517537
describe('as standalone', () => {
@@ -522,19 +542,18 @@ describe('MatButtonToggle without forms', () => {
522542
let buttonToggleInstance: MatButtonToggle;
523543
let testComponent: StandaloneButtonToggle;
524544

525-
beforeEach(async(() => {
545+
beforeEach(fakeAsync(() => {
526546
fixture = TestBed.createComponent(StandaloneButtonToggle);
527547
fixture.detectChanges();
528548

529-
testComponent = fixture.debugElement.componentInstance;
530-
549+
testComponent = fixture.componentInstance;
531550
buttonToggleDebugElement = fixture.debugElement.query(By.directive(MatButtonToggle));
532551
buttonToggleNativeElement = buttonToggleDebugElement.nativeElement;
533552
buttonToggleLabelElement = fixture.debugElement.query(By.css('label')).nativeElement;
534553
buttonToggleInstance = buttonToggleDebugElement.componentInstance;
535554
}));
536555

537-
it('should toggle when clicked', () => {
556+
it('should toggle when clicked', fakeAsync(() => {
538557
buttonToggleLabelElement.click();
539558

540559
fixture.detectChanges();
@@ -545,7 +564,7 @@ describe('MatButtonToggle without forms', () => {
545564
fixture.detectChanges();
546565

547566
expect(buttonToggleInstance.checked).toBe(false);
548-
});
567+
}));
549568

550569
it('should emit a change event for state changes', fakeAsync(() => {
551570

0 commit comments

Comments
 (0)