Skip to content

Commit 8cb54a9

Browse files
committed
test(material-experimental/mdc-slider): add two-way binding unit tests
1 parent 204a432 commit 8cb54a9

File tree

3 files changed

+120
-20
lines changed

3 files changed

+120
-20
lines changed

src/material-experimental/mdc-slider/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ ng_test_library(
7575
"//src/cdk/keycodes",
7676
"//src/cdk/platform",
7777
"//src/cdk/testing/private",
78+
"//src/cdk/testing/private/e2e",
7879
"//src/material/core",
7980
"@npm//@angular/forms",
8081
"@npm//@angular/platform-browser",

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

Lines changed: 110 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import {
1212
dispatchPointerEvent,
1313
dispatchTouchEvent,
1414
} from '@angular/cdk/testing/private';
15-
import {Component, Type} from '@angular/core';
15+
import {Point} from '@angular/cdk/testing/private/e2e';
16+
import {Component, QueryList, Type, ViewChild, ViewChildren} from '@angular/core';
1617
import {ComponentFixture, fakeAsync, TestBed, tick, waitForAsync} from '@angular/core/testing';
1718
import {By} from '@angular/platform-browser';
1819
import {Thumb} from '@material/slider';
@@ -759,6 +760,71 @@ describe('MDC-based MatSlider' , () => {
759760
expect(endInputInstance.value).toBe(70);
760761
});
761762
});
763+
764+
describe('slider with a two-way binding', () => {
765+
let fixture: ComponentFixture<SliderWithTwoWayBinding>;
766+
let testComponent: SliderWithTwoWayBinding;
767+
768+
beforeEach(() => {
769+
fixture = createComponent(SliderWithTwoWayBinding);
770+
fixture.detectChanges();
771+
testComponent = fixture.componentInstance;
772+
});
773+
774+
it('should sync the value binding in both directions', () => {
775+
expect(testComponent.value).toBe(0);
776+
expect(testComponent.sliderInput.value).toBe(0);
777+
778+
slideToValue(testComponent.slider, 10, Thumb.END, platform.IOS);
779+
expect(testComponent.value).toBe(10);
780+
expect(testComponent.sliderInput.value).toBe(10);
781+
782+
testComponent.value = 20;
783+
fixture.detectChanges();
784+
expect(testComponent.value).toBe(20);
785+
expect(testComponent.sliderInput.value).toBe(20);
786+
});
787+
});
788+
789+
describe('range slider with a two-way binding', () => {
790+
let fixture: ComponentFixture<RangeSliderWithTwoWayBinding>;
791+
let testComponent: RangeSliderWithTwoWayBinding;
792+
793+
beforeEach(waitForAsync(() => {
794+
fixture = createComponent(RangeSliderWithTwoWayBinding);
795+
fixture.detectChanges();
796+
testComponent = fixture.componentInstance;
797+
}));
798+
799+
it('should sync the start value binding in both directions', () => {
800+
expect(testComponent.startValue).toBe(0);
801+
expect(testComponent.sliderInputs.get(0)!.value).toBe(0);
802+
803+
slideToValue(testComponent.slider, 10, Thumb.START, platform.IOS);
804+
805+
expect(testComponent.startValue).toBe(10);
806+
expect(testComponent.sliderInputs.get(0)!.value).toBe(10);
807+
808+
testComponent.startValue = 20;
809+
fixture.detectChanges();
810+
expect(testComponent.startValue).toBe(20);
811+
expect(testComponent.sliderInputs.get(0)!.value).toBe(20);
812+
});
813+
814+
it('should sync the end value binding in both directions', () => {
815+
expect(testComponent.endValue).toBe(100);
816+
expect(testComponent.sliderInputs.get(1)!.value).toBe(100);
817+
818+
slideToValue(testComponent.slider, 90, Thumb.END, platform.IOS);
819+
expect(testComponent.endValue).toBe(90);
820+
expect(testComponent.sliderInputs.get(1)!.value).toBe(90);
821+
822+
testComponent.endValue = 80;
823+
fixture.detectChanges();
824+
expect(testComponent.endValue).toBe(80);
825+
expect(testComponent.sliderInputs.get(1)!.value).toBe(80);
826+
});
827+
});
762828
});
763829

764830

@@ -910,6 +976,34 @@ class RangeSliderWithOneWayBinding {
910976
endValue = 75;
911977
}
912978

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

9271021
/** Clicks on the MatSlider at the coordinates corresponding to the given value. */
9281022
function setValueByClick(slider: MatSlider, value: number, isIOS: boolean) {
929-
const {min, max} = slider;
930-
const percent = (value - min) / (max - min);
931-
9321023
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);
1024+
const {x, y} = getCoordsForValue(slider, value);
9361025

9371026
dispatchPointerOrTouchEvent(sliderElement, PointerEventType.POINTER_DOWN, x, y, isIOS);
9381027
dispatchPointerOrTouchEvent(sliderElement, PointerEventType.POINTER_UP, x, y, isIOS);
9391028
}
9401029

9411030
/** Slides the MatSlider's thumb to the given value. */
9421031
function slideToValue(slider: MatSlider, value: number, thumbPosition: Thumb, isIOS: boolean) {
943-
const {min, max} = slider;
944-
const percent = (value - min) / (max - min);
945-
9461032
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);
1033+
const {x: startX, y: startY} = getCoordsForValue(slider, slider._getInput(thumbPosition).value);
1034+
const {x: endX, y: endY} = getCoordsForValue(slider, value);
9571035

9581036
dispatchPointerOrTouchEvent(sliderElement, PointerEventType.POINTER_DOWN, startX, startY, isIOS);
9591037
dispatchPointerOrTouchEvent(sliderElement, PointerEventType.POINTER_MOVE, endX, endY, isIOS);
9601038
dispatchPointerOrTouchEvent(sliderElement, PointerEventType.POINTER_UP, endX, endY, isIOS);
9611039
}
9621040

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

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

Lines changed: 9 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,8 @@ class SliderAdapter implements MDCSliderAdapter {
954961
const input = this._delegate._getInput(thumbPosition);
955962
input._emitFakeEvent('change');
956963
input._onChange(value);
964+
console.log('thumb position:', thumbPosition);
965+
input.valueChange.emit(value);
957966
}
958967
emitInputEvent = (value: number, thumbPosition: Thumb): void => {
959968
this._delegate._getInput(thumbPosition)._emitFakeEvent('input');

0 commit comments

Comments
 (0)