Skip to content

Commit 40a2f98

Browse files
authored
test(material-experimental/mdc-slider): add custom form control tests (#22546)
* test(material-experimental/mdc-slider): add custom form control tests * fix bug where setting the disabled state on the overall slider was not disabling the individual slider thumbs control value accessor disabled state.
1 parent 4bbbf18 commit 40a2f98

File tree

2 files changed

+274
-11
lines changed

2 files changed

+274
-11
lines changed

src/material-experimental/mdc-slider/slider.spec.ts

Lines changed: 243 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
tick,
2222
waitForAsync,
2323
} from '@angular/core/testing';
24-
import {FormsModule} from '@angular/forms';
24+
import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
2525
import {By} from '@angular/platform-browser';
2626
import {Thumb} from '@material/slider';
2727
import {MatSliderModule} from './module';
@@ -43,7 +43,7 @@ describe('MDC-based MatSlider' , () => {
4343

4444
function createComponent<T>(component: Type<T>): ComponentFixture<T> {
4545
TestBed.configureTestingModule({
46-
imports: [FormsModule, MatSliderModule],
46+
imports: [FormsModule, MatSliderModule, ReactiveFormsModule],
4747
declarations: [component],
4848
}).compileComponents();
4949
return TestBed.createComponent<T>(component);
@@ -1283,6 +1283,225 @@ describe('MDC-based MatSlider' , () => {
12831283
}));
12841284
});
12851285

1286+
describe('slider as a custom form control', () => {
1287+
let fixture: ComponentFixture<SliderWithFormControl>;
1288+
let testComponent: SliderWithFormControl;
1289+
let sliderInstance: MatSlider;
1290+
let inputInstance: MatSliderThumb;
1291+
1292+
beforeEach(waitForAsync(() => {
1293+
fixture = createComponent(SliderWithFormControl);
1294+
fixture.detectChanges();
1295+
testComponent = fixture.debugElement.componentInstance;
1296+
const sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider));
1297+
sliderInstance = sliderDebugElement.componentInstance;
1298+
inputInstance = sliderInstance._getInput(Thumb.END);
1299+
}));
1300+
1301+
it('should not update the control when the value is updated', () => {
1302+
expect(testComponent.control.value).toBe(0);
1303+
inputInstance.value = 11;
1304+
fixture.detectChanges();
1305+
expect(testComponent.control.value).toBe(0);
1306+
});
1307+
1308+
it('should update the control on mouseup', () => {
1309+
expect(testComponent.control.value).toBe(0);
1310+
setValueByClick(sliderInstance, 76, platform.IOS);
1311+
expect(testComponent.control.value).toBe(76);
1312+
});
1313+
1314+
it('should update the control on slide', () => {
1315+
expect(testComponent.control.value).toBe(0);
1316+
slideToValue(sliderInstance, 19, Thumb.END, platform.IOS);
1317+
expect(testComponent.control.value).toBe(19);
1318+
});
1319+
1320+
it('should update the value when the control is set', () => {
1321+
expect(inputInstance.value).toBe(0);
1322+
testComponent.control.setValue(7);
1323+
expect(inputInstance.value).toBe(7);
1324+
});
1325+
1326+
it('should update the disabled state when control is disabled', () => {
1327+
expect(sliderInstance.disabled).toBe(false);
1328+
testComponent.control.disable();
1329+
expect(sliderInstance.disabled).toBe(true);
1330+
});
1331+
1332+
it('should update the disabled state when the control is enabled', () => {
1333+
sliderInstance.disabled = true;
1334+
testComponent.control.enable();
1335+
expect(sliderInstance.disabled).toBe(false);
1336+
});
1337+
1338+
it('should have the correct control state initially and after interaction', () => {
1339+
let sliderControl = testComponent.control;
1340+
1341+
// The control should start off valid, pristine, and untouched.
1342+
expect(sliderControl.valid).toBe(true);
1343+
expect(sliderControl.pristine).toBe(true);
1344+
expect(sliderControl.touched).toBe(false);
1345+
1346+
// After changing the value, the control should become dirty (not pristine),
1347+
// but remain untouched.
1348+
setValueByClick(sliderInstance, 50, platform.IOS);
1349+
1350+
expect(sliderControl.valid).toBe(true);
1351+
expect(sliderControl.pristine).toBe(false);
1352+
expect(sliderControl.touched).toBe(false);
1353+
1354+
// If the control has been visited due to interaction, the control should remain
1355+
// dirty and now also be touched.
1356+
inputInstance.blur();
1357+
fixture.detectChanges();
1358+
1359+
expect(sliderControl.valid).toBe(true);
1360+
expect(sliderControl.pristine).toBe(false);
1361+
expect(sliderControl.touched).toBe(true);
1362+
});
1363+
});
1364+
1365+
describe('slider as a custom form control', () => {
1366+
let fixture: ComponentFixture<RangeSliderWithFormControl>;
1367+
let testComponent: RangeSliderWithFormControl;
1368+
let sliderInstance: MatSlider;
1369+
let startInputInstance: MatSliderThumb;
1370+
let endInputInstance: MatSliderThumb;
1371+
1372+
beforeEach(waitForAsync(() => {
1373+
fixture = createComponent(RangeSliderWithFormControl);
1374+
fixture.detectChanges();
1375+
testComponent = fixture.debugElement.componentInstance;
1376+
const sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider));
1377+
sliderInstance = sliderDebugElement.componentInstance;
1378+
startInputInstance = sliderInstance._getInput(Thumb.START);
1379+
endInputInstance = sliderInstance._getInput(Thumb.END);
1380+
}));
1381+
1382+
it('should not update the start input control when the value is updated', () => {
1383+
expect(testComponent.startInputControl.value).toBe(0);
1384+
startInputInstance.value = 11;
1385+
fixture.detectChanges();
1386+
expect(testComponent.startInputControl.value).toBe(0);
1387+
});
1388+
1389+
it('should not update the end input control when the value is updated', () => {
1390+
expect(testComponent.endInputControl.value).toBe(100);
1391+
endInputInstance.value = 11;
1392+
fixture.detectChanges();
1393+
expect(testComponent.endInputControl.value).toBe(100);
1394+
});
1395+
1396+
it('should update the start input control on mouseup', () => {
1397+
expect(testComponent.startInputControl.value).toBe(0);
1398+
setValueByClick(sliderInstance, 20, platform.IOS);
1399+
expect(testComponent.startInputControl.value).toBe(20);
1400+
});
1401+
1402+
it('should update the end input control on mouseup', () => {
1403+
expect(testComponent.endInputControl.value).toBe(100);
1404+
setValueByClick(sliderInstance, 80, platform.IOS);
1405+
expect(testComponent.endInputControl.value).toBe(80);
1406+
});
1407+
1408+
it('should update the start input control on slide', () => {
1409+
expect(testComponent.startInputControl.value).toBe(0);
1410+
slideToValue(sliderInstance, 20, Thumb.START, platform.IOS);
1411+
expect(testComponent.startInputControl.value).toBe(20);
1412+
});
1413+
1414+
it('should update the end input control on slide', () => {
1415+
expect(testComponent.endInputControl.value).toBe(100);
1416+
slideToValue(sliderInstance, 80, Thumb.END, platform.IOS);
1417+
expect(testComponent.endInputControl.value).toBe(80);
1418+
});
1419+
1420+
it('should update the start input value when the start input control is set', () => {
1421+
expect(startInputInstance.value).toBe(0);
1422+
testComponent.startInputControl.setValue(10);
1423+
expect(startInputInstance.value).toBe(10);
1424+
});
1425+
1426+
it('should update the end input value when the end input control is set', () => {
1427+
expect(endInputInstance.value).toBe(100);
1428+
testComponent.endInputControl.setValue(90);
1429+
expect(endInputInstance.value).toBe(90);
1430+
});
1431+
1432+
it('should update the disabled state if the start input control is disabled', () => {
1433+
expect(sliderInstance.disabled).toBe(false);
1434+
testComponent.startInputControl.disable();
1435+
expect(sliderInstance.disabled).toBe(true);
1436+
});
1437+
1438+
it('should update the disabled state if the end input control is disabled', () => {
1439+
expect(sliderInstance.disabled).toBe(false);
1440+
testComponent.endInputControl.disable();
1441+
expect(sliderInstance.disabled).toBe(true);
1442+
});
1443+
1444+
it('should update the disabled state when both input controls are enabled', () => {
1445+
sliderInstance.disabled = true;
1446+
testComponent.startInputControl.enable();
1447+
expect(sliderInstance.disabled).toBe(true);
1448+
testComponent.endInputControl.enable();
1449+
expect(sliderInstance.disabled).toBe(false);
1450+
});
1451+
1452+
it('should have the correct start input control state initially and after interaction', () => {
1453+
let sliderControl = testComponent.startInputControl;
1454+
1455+
// The control should start off valid, pristine, and untouched.
1456+
expect(sliderControl.valid).toBe(true);
1457+
expect(sliderControl.pristine).toBe(true);
1458+
expect(sliderControl.touched).toBe(false);
1459+
1460+
// After changing the value, the control should become dirty (not pristine),
1461+
// but remain untouched.
1462+
setValueByClick(sliderInstance, 25, platform.IOS);
1463+
1464+
expect(sliderControl.valid).toBe(true);
1465+
expect(sliderControl.pristine).toBe(false);
1466+
expect(sliderControl.touched).toBe(false);
1467+
1468+
// If the control has been visited due to interaction, the control should remain
1469+
// dirty and now also be touched.
1470+
startInputInstance.blur();
1471+
fixture.detectChanges();
1472+
1473+
expect(sliderControl.valid).toBe(true);
1474+
expect(sliderControl.pristine).toBe(false);
1475+
expect(sliderControl.touched).toBe(true);
1476+
});
1477+
1478+
it('should have the correct start input control state initially and after interaction', () => {
1479+
let sliderControl = testComponent.endInputControl;
1480+
1481+
// The control should start off valid, pristine, and untouched.
1482+
expect(sliderControl.valid).toBe(true);
1483+
expect(sliderControl.pristine).toBe(true);
1484+
expect(sliderControl.touched).toBe(false);
1485+
1486+
// After changing the value, the control should become dirty (not pristine),
1487+
// but remain untouched.
1488+
setValueByClick(sliderInstance, 75, platform.IOS);
1489+
1490+
expect(sliderControl.valid).toBe(true);
1491+
expect(sliderControl.pristine).toBe(false);
1492+
expect(sliderControl.touched).toBe(false);
1493+
1494+
// If the control has been visited due to interaction, the control should remain
1495+
// dirty and now also be touched.
1496+
endInputInstance.blur();
1497+
fixture.detectChanges();
1498+
1499+
expect(sliderControl.valid).toBe(true);
1500+
expect(sliderControl.pristine).toBe(false);
1501+
expect(sliderControl.touched).toBe(true);
1502+
});
1503+
});
1504+
12861505
describe('slider with a two-way binding', () => {
12871506
let fixture: ComponentFixture<SliderWithTwoWayBinding>;
12881507
let testComponent: SliderWithTwoWayBinding;
@@ -1559,6 +1778,28 @@ class RangeSliderWithNgModel {
15591778
endVal: number | undefined = 100;
15601779
}
15611780

1781+
@Component({
1782+
template: `
1783+
<mat-slider>
1784+
<input [formControl]="control" matSliderThumb>
1785+
</mat-slider>`,
1786+
})
1787+
class SliderWithFormControl {
1788+
control = new FormControl(0);
1789+
}
1790+
1791+
@Component({
1792+
template: `
1793+
<mat-slider>
1794+
<input [formControl]="startInputControl" matSliderStartThumb>
1795+
<input [formControl]="endInputControl" matSliderEndThumb>
1796+
</mat-slider>`,
1797+
})
1798+
class RangeSliderWithFormControl {
1799+
startInputControl = new FormControl(0);
1800+
endInputControl = new FormControl(100);
1801+
}
1802+
15621803
@Component({
15631804
template: `
15641805
<mat-slider>

src/material-experimental/mdc-slider/slider.ts

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,11 @@ export class MatSliderThumb implements AfterViewInit, ControlValueAccessor, OnIn
335335
/** Event emitted on each value change that happens to the slider. */
336336
@Output() readonly input: EventEmitter<Event> = new EventEmitter<Event>();
337337

338+
/**
339+
* Used to determine the disabled state of the MatSlider (ControlValueAccessor).
340+
* For ranged sliders, the disabled state of the MatSlider depends on the combined state of the
341+
* start and end inputs. See MatSlider._updateDisabled.
342+
*/
338343
_disabled: boolean = false;
339344

340345
/**
@@ -546,14 +551,8 @@ export class MatSlider extends _MatSliderMixinBase
546551
@Input()
547552
get disabled(): boolean { return this._disabled; }
548553
set disabled(v: boolean) {
549-
this._disabled = coerceBooleanProperty(v);
550-
551-
// If we want to disable the slider after the foundation has been initialized,
552-
// we need to inform the foundation by calling `setDisabled`. Also, we can't call
553-
// this before initializing the foundation because it will throw errors.
554-
if (this._initialized) {
555-
this._foundation.setDisabled(v);
556-
}
554+
this._setDisabled(coerceBooleanProperty(v));
555+
this._updateInputsDisabledState();
557556
}
558557
private _disabled: boolean = false;
559558

@@ -709,14 +708,37 @@ export class MatSlider extends _MatSliderMixinBase
709708
: this._foundation.setValue(value);
710709
}
711710

711+
/** Sets the disabled state of the MatSlider. */
712+
private _setDisabled(value: boolean) {
713+
this._disabled = value;
714+
715+
// If we want to disable the slider after the foundation has been initialized,
716+
// we need to inform the foundation by calling `setDisabled`. Also, we can't call
717+
// this before initializing the foundation because it will throw errors.
718+
if (this._initialized) {
719+
this._foundation.setDisabled(value);
720+
}
721+
}
722+
723+
/** Sets the disabled state of the individual slider thumb(s) (ControlValueAccessor). */
724+
private _updateInputsDisabledState() {
725+
if (this._initialized) {
726+
this._getInput(Thumb.END)._disabled = true;
727+
if (this._isRange()) {
728+
this._getInput(Thumb.START)._disabled = true;
729+
}
730+
}
731+
}
732+
712733
/** Whether this is a ranged slider. */
713734
_isRange(): boolean {
714735
return this._inputs.length === 2;
715736
}
716737

717738
/** Sets the disabled state based on the disabled state of the inputs (ControlValueAccessor). */
718739
_updateDisabled(): void {
719-
this.disabled = this._inputs.some(input => input._disabled);
740+
const disabled = this._inputs.some(input => input._disabled);
741+
this._setDisabled(disabled);
720742
}
721743

722744
/** Gets the slider thumb input of the given thumb position. */

0 commit comments

Comments
 (0)