Skip to content

Commit 7ac106a

Browse files
authored
test(material-experimental/mdc-slider): add two-way binding unit tests (#22470)
1 parent 204a432 commit 7ac106a

File tree

2 files changed

+122
-20
lines changed

2 files changed

+122
-20
lines changed

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

Lines changed: 114 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,18 @@ import {
1212
dispatchPointerEvent,
1313
dispatchTouchEvent,
1414
} from '@angular/cdk/testing/private';
15-
import {Component, Type} from '@angular/core';
15+
import {Component, QueryList, Type, ViewChild, ViewChildren} from '@angular/core';
1616
import {ComponentFixture, fakeAsync, TestBed, tick, waitForAsync} from '@angular/core/testing';
1717
import {By} from '@angular/platform-browser';
1818
import {Thumb} from '@material/slider';
1919
import {MatSliderModule} from './module';
2020
import {MatSlider, MatSliderThumb, MatSliderVisualThumb} from './slider';
2121

22+
interface Point {
23+
x: number;
24+
y: number;
25+
}
26+
2227
describe('MDC-based MatSlider' , () => {
2328
let platform: Platform;
2429

@@ -759,6 +764,71 @@ describe('MDC-based MatSlider' , () => {
759764
expect(endInputInstance.value).toBe(70);
760765
});
761766
});
767+
768+
describe('slider with a two-way binding', () => {
769+
let fixture: ComponentFixture<SliderWithTwoWayBinding>;
770+
let testComponent: SliderWithTwoWayBinding;
771+
772+
beforeEach(() => {
773+
fixture = createComponent(SliderWithTwoWayBinding);
774+
fixture.detectChanges();
775+
testComponent = fixture.componentInstance;
776+
});
777+
778+
it('should sync the value binding in both directions', () => {
779+
expect(testComponent.value).toBe(0);
780+
expect(testComponent.sliderInput.value).toBe(0);
781+
782+
slideToValue(testComponent.slider, 10, Thumb.END, platform.IOS);
783+
expect(testComponent.value).toBe(10);
784+
expect(testComponent.sliderInput.value).toBe(10);
785+
786+
testComponent.value = 20;
787+
fixture.detectChanges();
788+
expect(testComponent.value).toBe(20);
789+
expect(testComponent.sliderInput.value).toBe(20);
790+
});
791+
});
792+
793+
describe('range slider with a two-way binding', () => {
794+
let fixture: ComponentFixture<RangeSliderWithTwoWayBinding>;
795+
let testComponent: RangeSliderWithTwoWayBinding;
796+
797+
beforeEach(waitForAsync(() => {
798+
fixture = createComponent(RangeSliderWithTwoWayBinding);
799+
fixture.detectChanges();
800+
testComponent = fixture.componentInstance;
801+
}));
802+
803+
it('should sync the start value binding in both directions', () => {
804+
expect(testComponent.startValue).toBe(0);
805+
expect(testComponent.sliderInputs.get(0)!.value).toBe(0);
806+
807+
slideToValue(testComponent.slider, 10, Thumb.START, platform.IOS);
808+
809+
expect(testComponent.startValue).toBe(10);
810+
expect(testComponent.sliderInputs.get(0)!.value).toBe(10);
811+
812+
testComponent.startValue = 20;
813+
fixture.detectChanges();
814+
expect(testComponent.startValue).toBe(20);
815+
expect(testComponent.sliderInputs.get(0)!.value).toBe(20);
816+
});
817+
818+
it('should sync the end value binding in both directions', () => {
819+
expect(testComponent.endValue).toBe(100);
820+
expect(testComponent.sliderInputs.get(1)!.value).toBe(100);
821+
822+
slideToValue(testComponent.slider, 90, Thumb.END, platform.IOS);
823+
expect(testComponent.endValue).toBe(90);
824+
expect(testComponent.sliderInputs.get(1)!.value).toBe(90);
825+
826+
testComponent.endValue = 80;
827+
fixture.detectChanges();
828+
expect(testComponent.endValue).toBe(80);
829+
expect(testComponent.sliderInputs.get(1)!.value).toBe(80);
830+
});
831+
});
762832
});
763833

764834

@@ -910,6 +980,34 @@ class RangeSliderWithOneWayBinding {
910980
endValue = 75;
911981
}
912982

983+
@Component({
984+
template: `
985+
<mat-slider>
986+
<input [(value)]="value" matSliderThumb>
987+
</mat-slider>
988+
`,
989+
})
990+
class SliderWithTwoWayBinding {
991+
@ViewChild(MatSlider) slider: MatSlider;
992+
@ViewChild(MatSliderThumb) sliderInput: MatSliderThumb;
993+
value = 0;
994+
}
995+
996+
@Component({
997+
template: `
998+
<mat-slider>
999+
<input [(value)]="startValue" matSliderStartThumb>
1000+
<input [(value)]="endValue" matSliderEndThumb>
1001+
</mat-slider>
1002+
`,
1003+
})
1004+
class RangeSliderWithTwoWayBinding {
1005+
@ViewChild(MatSlider) slider: MatSlider;
1006+
@ViewChildren(MatSliderThumb) sliderInputs: QueryList<MatSliderThumb>;
1007+
startValue = 0;
1008+
endValue = 100;
1009+
}
1010+
9131011
/** The pointer event types used by the MDC Slider. */
9141012
const enum PointerEventType {
9151013
POINTER_DOWN = 'pointerdown',
@@ -926,40 +1024,36 @@ const enum TouchEventType {
9261024

9271025
/** Clicks on the MatSlider at the coordinates corresponding to the given value. */
9281026
function setValueByClick(slider: MatSlider, value: number, isIOS: boolean) {
929-
const {min, max} = slider;
930-
const percent = (value - min) / (max - min);
931-
9321027
const sliderElement = slider._elementRef.nativeElement;
933-
const {top, left, width, height} = sliderElement.getBoundingClientRect();
934-
const x = left + (width * percent);
935-
const y = top + (height / 2);
1028+
const {x, y} = getCoordsForValue(slider, value);
9361029

9371030
dispatchPointerOrTouchEvent(sliderElement, PointerEventType.POINTER_DOWN, x, y, isIOS);
9381031
dispatchPointerOrTouchEvent(sliderElement, PointerEventType.POINTER_UP, x, y, isIOS);
9391032
}
9401033

9411034
/** Slides the MatSlider's thumb to the given value. */
9421035
function slideToValue(slider: MatSlider, value: number, thumbPosition: Thumb, isIOS: boolean) {
943-
const {min, max} = slider;
944-
const percent = (value - min) / (max - min);
945-
9461036
const sliderElement = slider._elementRef.nativeElement;
947-
const thumbElement = slider._getThumbElement(thumbPosition);
948-
949-
const sliderDimensions = sliderElement.getBoundingClientRect();
950-
const thumbDimensions = thumbElement.getBoundingClientRect();
951-
952-
const startX = thumbDimensions.left + (thumbDimensions.width / 2);
953-
const startY = thumbDimensions.top + (thumbDimensions.height / 2);
954-
955-
const endX = sliderDimensions.left + (sliderDimensions.width * percent);
956-
const endY = sliderDimensions.top + (sliderDimensions.height / 2);
1037+
const {x: startX, y: startY} = getCoordsForValue(slider, slider._getInput(thumbPosition).value);
1038+
const {x: endX, y: endY} = getCoordsForValue(slider, value);
9571039

9581040
dispatchPointerOrTouchEvent(sliderElement, PointerEventType.POINTER_DOWN, startX, startY, isIOS);
9591041
dispatchPointerOrTouchEvent(sliderElement, PointerEventType.POINTER_MOVE, endX, endY, isIOS);
9601042
dispatchPointerOrTouchEvent(sliderElement, PointerEventType.POINTER_UP, endX, endY, isIOS);
9611043
}
9621044

1045+
/** Returns the x and y coordinates for the given slider value. */
1046+
function getCoordsForValue(slider: MatSlider, value: number): Point {
1047+
const {min, max} = slider;
1048+
const percent = (value - min) / (max - min);
1049+
1050+
const {top, left, width, height} = slider._elementRef.nativeElement.getBoundingClientRect();
1051+
const x = left + (width * percent);
1052+
const y = top + (height / 2);
1053+
1054+
return {x, y};
1055+
}
1056+
9631057
/** Dispatch a pointerdown or pointerup event if supported, otherwise dispatch the touch event. */
9641058
function dispatchPointerOrTouchEvent(
9651059
node: Node, type: PointerEventType, x: number, y: number, isIOS: boolean) {

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,13 @@ export class MatSliderThumb implements AfterViewInit, ControlValueAccessor, OnIn
308308
}
309309
}
310310

311+
/**
312+
* Emits when the raw value of the slider changes. This is here primarily
313+
* to facilitate the two-way binding for the `value` input.
314+
* @docs-private
315+
*/
316+
@Output() readonly valueChange: EventEmitter<number> = new EventEmitter<number>();
317+
311318
/** Event emitted when the slider thumb starts being dragged. */
312319
@Output() readonly dragStart: EventEmitter<MatSliderDragEvent>
313320
= new EventEmitter<MatSliderDragEvent>();
@@ -954,6 +961,7 @@ class SliderAdapter implements MDCSliderAdapter {
954961
const input = this._delegate._getInput(thumbPosition);
955962
input._emitFakeEvent('change');
956963
input._onChange(value);
964+
input.valueChange.emit(value);
957965
}
958966
emitInputEvent = (value: number, thumbPosition: Thumb): void => {
959967
this._delegate._getInput(thumbPosition)._emitFakeEvent('input');

0 commit comments

Comments
 (0)