/* ************************* Lock To Ticks Tests ************************* Verify that the when lock_to_ticks is true, the selected values will be snapped to closest tick values */ describe("Slider with lock_to_ticks: true tests", function() { var testSlider; it("Should set `options.lock_to_ticks` to false when `ticks[]` is not set", function() { testSlider = $('#testSlider1').slider({ lock_to_ticks: true }); var lockAttr = testSlider.slider('getAttribute', 'lock_to_ticks'); expect(lockAttr).toBe(false); }); it("Should set the slider value when `ticks[]` is not set", function() { testSlider = $('#testSlider1').slider({ min: 0, max: 10, value: 1, lock_to_ticks: true }); testSlider.slider('setValue', 5); var value = testSlider.slider('getValue'); expect(value).toBe(5); }); it("Should return a value (after calling `setAttribute()`)", function() { testSlider = $('#testSlider1').slider({ min: 0, max: 10, value: 1, lock_to_ticks: true }); // Try to lock to ticks when there are no ticks[] testSlider.slider('setAttribute', 'lock_to_ticks', true); testSlider.slider('setValue', 5); // Without proper checking, getValue() is going to return NaN in this case var value = testSlider.slider('getValue'); expect(isNaN(value)).toBe(false); }); it("Should snap to closest tick value (single)", function() { testSlider = $('#testSlider1').slider({ value: 1, ticks: [1, 10, 50, 200, 500, 1000, 5000, 10000], lock_to_ticks: true }); // Set a value that is not a tick testSlider.slider('setValue', 102); var value = testSlider.slider('getValue'); expect(value).toBe(50); }); it("Should snap to closest tick value (single, vertical)", function() { testSlider = $('#testSlider1').slider({ value: 1, ticks: [1, 10, 50, 200, 500, 1000, 5000, 10000], lock_to_ticks: true, orientation: 'vertical' }); //selecting values that are not inside tickes testSlider.slider('setValue', 102); //getting the actual values. They should be the closest values from ticks (which are 1 and 50 on this case) var value = testSlider.slider('getValue'); expect(value).toBe(50); }); it("Should snap to closest tick value (range)", function() { testSlider = $("#testSlider1").slider({ value: [1, 10000], ticks: [1, 10, 50, 200, 500, 1000, 5000, 10000], lock_to_ticks: true }); //selecting values that are not inside tickes testSlider.slider("setValue", [4, 102]); //getting the actual values. They should be the closest values from ticks (which are 1 and 50 on this case) var values = testSlider.slider("getValue"); expect(values[0]).toBe(1); expect(values[1]).toBe(50); }); it("Should snap to closest tick value (range, vertical)", function() { testSlider = $("#testSlider1").slider({ value: [1, 10000], ticks: [1, 10, 50, 200, 500, 1000, 5000, 10000], lock_to_ticks: true, orientation: 'vertical' }); //selecting values that are not inside tickes testSlider.slider("setValue", [4, 102]); //getting the actual values. They should be the closest values from ticks (which are 1 and 50 on this case) var values = testSlider.slider("getValue"); expect(values[0]).toBe(1); expect(values[1]).toBe(50); }); afterEach(function() { if(testSlider) { testSlider.slider('destroy'); testSlider = null; } }); }); /** * The mouse navigation tests are based on the following slider properties: * * initial value: 3 or [3, 7] * ticks: [0, 3, 5, 7, 10] * step: 0.1 * * When the values are in the range from 0 to 10 and the step is 0.1, every value * can be represented as 1% of the range. For example, 5.5 is 55% and 5.6 is 56%. * This makes it easier to test the behaviour of tick locking. * * The mouse clicks are based on a percentage that represents where the user clicked * on the slider (60% = 6.0). The percentage is then used to calculate the coordinates * for the mouse events (mousedown, mousemove, and mouseup). * * These tests are setup to test for all combinations of relevant slider configurations: * single/range, horizontal/vertical orientation, LTR/RTL, and ordered/reversed. * The tests also check that the handles have the correct positioning, which should * match the ticks that the handles lock to. * * The test data was carefully chosen based on following the test logic below. * * The test logic for sliders: * - Clicking to the left of the handle should not change its value * - Clicking to the left of the handle should change its value and lock to another tick * - Ditto for clicking to the right of the handle * * For range sliders, the same logic is applied except test both handle1 and handle2. */ describe("`lock_to_ticks: true` mouse navigation test cases", function() { var initialValue = 3; var initialRangeValues = [3, 7]; var tickValues = [0, 3, 5, 7, 10]; // Quick lookup from value to index var tickIndexes = {0: 0, 3: 1, 5: 2, 7: 3, 10: 4}; var stepValue = 0.1; var orientations = ['horizontal', 'vertical']; var reversed = [false, true]; var sliderTypes = ['single', 'range']; var rtl = [false, true]; var testCases = []; var mouseEventArguments; function calcMouseEventCoords(sliderElem, orientation, per) { var sliderBB = sliderElem.getBoundingClientRect(); if (orientation === 'horizontal') { return { clientX: sliderBB.left + (sliderElem.offsetWidth * per / 100), clientY: sliderBB.top }; } else if (orientation === 'vertical') { return { clientX: sliderBB.left, clientY: sliderBB.top + (sliderElem.offsetHeight * per / 100) }; } } beforeEach(function() { // Set up default set of mouse event arguments mouseEventArguments = [ 'mousemove', // type true, // canBubble true, // cancelable document, // view, 0, // detail 0, // screenX 0, // screenY undefined, // clientX undefined, // clientY, false, // ctrlKey false, // altKey false, // shiftKey false, // metaKey, 0, // button null // relatedTarget ]; }); function createMouseEvent(type, clientX, clientY) { var mouseEvent = document.createEvent('MouseEvent'); mouseEventArguments[0] = type; mouseEventArguments[7] = clientX; mouseEventArguments[8] = clientY; mouseEvent.initMouseEvent.apply(mouseEvent, mouseEventArguments); return mouseEvent; } var mouseTestData = { single: { testData: [{ willChange: false, expectedValue: initialValue, percent: 20 }, { willChange: true, expectedValue: 0, percent: 10 }, { willChange: false, expectedValue: initialValue, percent: 40 }, { willChange: true, expectedValue: 5, percent: 45 }], // Modify test data for RTL and reversed situations. altTestData: [{ willChange: false, expectedValue: initialValue, percent: 80 }, { willChange: true, expectedValue: 0, percent: 90 }, { willChange: false, expectedValue: initialValue, percent: 60 }, { willChange: true, expectedValue: 5, percent: 55 }], }, range: { testData: [{ willChange: false, expectedValue: initialRangeValues, percent: 20 }, { willChange: true, expectedValue: [0, 7], percent: 10 }, { willChange: false, expectedValue: initialRangeValues, percent: 40 }, { willChange: true, expectedValue: [5, 7], percent: 45 }, // More test data that will affect handle2 { willChange: true, expectedValue: [3, 5], percent: 60 }, { willChange: false, expectedValue: initialRangeValues, percent: 65 }, { willChange: false, expectedValue: initialRangeValues, percent: 80 }, { willChange: true, expectedValue: [3, 10], percent: 90 }], // Modify test data for RTL and reversed situations. // Here, the order of the ticks matter when locking a handle to a tick // when there are two ticks that are equal in distance to lock to. altTestData: [{ willChange: false, expectedValue: initialRangeValues, percent: 20 }, { willChange: true, expectedValue: [3, 10], percent: 10 }, { willChange: true, expectedValue: [3, 5], percent: 40 }, { willChange: false, expectedValue: initialRangeValues, percent: 35 }, // More test data that will affect handle2 { willChange: false, expectedValue: initialRangeValues, percent: 60 }, { willChange: true, expectedValue: [5, 7], percent: 55 }, { willChange: false, expectedValue: initialRangeValues, percent: 80 }, { willChange: true, expectedValue: [0, 7], percent: 90 }], } }; sliderTypes.forEach(function(sliderType) { orientations.forEach(function(orientation) { rtl.forEach(function(isRTL) { reversed.forEach(function(isReversed) { var isHorizontal = orientation === 'horizontal'; var isVertical = orientation === 'vertical'; var isRange = sliderType === 'range'; var whichData; var whichStyle; whichData = mouseTestData[sliderType].testData; if (isHorizontal) { // XOR // One or the other needs to be true if ((isRTL && !isReversed) || (isReversed && !isRTL)) { whichData = mouseTestData[sliderType].altTestData; } } else if (isVertical) { if (isReversed) { whichData = mouseTestData[sliderType].altTestData; } } if (isHorizontal) { if (isRTL) { whichStyle = 'right'; } else { whichStyle = 'left'; } } else if (isVertical) { whichStyle = 'top'; } testCases.push({ value: isRange ? initialRangeValues : initialValue, step: stepValue, range: isRange, orientation: orientation, reversed: isReversed, rtl: 'auto', isRTL: isRTL, inputId: isRTL ? 'rtlSlider' : 'testSlider1', testData: whichData, stylePos: whichStyle }); }); }); }); }); testCases.forEach(function(testCase) { describe("range=" + testCase.range + ", orientation=" + testCase.orientation + ", rtl=" + testCase.isRTL + ", reversed=" + testCase.reversed, function() { var $testSlider; var $handle1; var $handle2; var sliderId; var sliderOptions; var $tick1; var $tick2; var $ticks; beforeEach(function() { sliderId = testCase.range ? 'myRangeSlider' : 'mySlider'; sliderOptions = { id: sliderId, step: testCase.step, orientation: testCase.orientation, value: testCase.value, range: testCase.range, lock_to_ticks: true, reversed: testCase.reversed, rtl: 'auto', ticks: tickValues, }; }); afterEach(function() { if ($testSlider) { $testSlider.slider('destroy'); $testSlider = null; } }); testCase.testData.forEach(function(testData) { it("Should snap to closest tick (percent=" + testData.percent + ", changed=" + testData.willChange + ")", function(done) { $testSlider = $('#'+testCase.inputId).slider(sliderOptions); var sliderElem = $('#'+sliderId)[0]; $ticks = $('#'+sliderId).find('.slider-tick'); $handle1 = $('#'+sliderId).find('.slider-handle:first'); $handle2 = $('#'+sliderId).find('.slider-handle:last'); var coords = calcMouseEventCoords(sliderElem, testCase.orientation, testData.percent); var checkMouseMove = function() { var value = $testSlider.slider('getValue'); expect(value).toEqual(testData.expectedValue); if (testCase.range) { $tick1 = $ticks.eq(tickIndexes[testData.expectedValue[0]]); $tick2 = $ticks.eq(tickIndexes[testData.expectedValue[1]]); expect($handle1.css(testCase.stylePos)).toBe($tick1.css(testCase.stylePos)); expect($handle2.css(testCase.stylePos)).toBe($tick2.css(testCase.stylePos)); } else { $tick1 = $ticks.eq(tickIndexes[testData.expectedValue]); expect($handle1.css(testCase.stylePos)).toBe($tick1.css(testCase.stylePos)); } }; var checkMouseUp = function() { var value = $testSlider.slider('getValue'); expect(value).toEqual(testData.expectedValue); if (testCase.range) { $tick1 = $ticks.eq(tickIndexes[testData.expectedValue[0]]); $tick2 = $ticks.eq(tickIndexes[testData.expectedValue[1]]); expect($handle1.css(testCase.stylePos)).toBe($tick1.css(testCase.stylePos)); expect($handle2.css(testCase.stylePos)).toBe($tick2.css(testCase.stylePos)); } else { $tick1 = $ticks.eq(tickIndexes[testData.expectedValue]); expect($handle1.css(testCase.stylePos)).toBe($tick1.css(testCase.stylePos)); } document.removeEventListener('mousemove', checkMouseMove, false); document.removeEventListener('mouseup', checkMouseUp, false); done(); }; sliderElem.addEventListener('mousedown', function() { var value = $testSlider.slider('getValue'); expect(value).toEqual(testData.expectedValue); // Check position of handles if (testCase.range) { $tick1 = $ticks.eq(tickIndexes[testData.expectedValue[0]]); $tick2 = $ticks.eq(tickIndexes[testData.expectedValue[1]]); expect($handle1.css(testCase.stylePos)).toBe($tick1.css(testCase.stylePos)); expect($handle2.css(testCase.stylePos)).toBe($tick2.css(testCase.stylePos)); } else { $tick1 = $ticks.eq(tickIndexes[testData.expectedValue]); expect($handle1.css(testCase.stylePos)).toBe($tick1.css(testCase.stylePos)); } document.addEventListener('mousemove', checkMouseMove, false); document.addEventListener('mouseup', checkMouseUp, false); }); sliderElem.dispatchEvent(createMouseEvent('mousedown', coords.clientX, coords.clientY)); document.dispatchEvent(createMouseEvent('mousemove', coords.clientX, coords.clientY)); document.dispatchEvent(createMouseEvent('mouseup', coords.clientX, coords.clientY)); }); }); }); }); }); /** * The keyboard navigation tests are based on the following slider properties: * * initial value: 3 or [3, 7] * ticks: [0, 3, 5, 7, 10] * step: 0.1 * * See description for mouse navigation tests for explanation for test setup values. * * These tests are setup to test for all combinations of relevant slider configurations: * single/range, horizontal/vertical orientation, LTR/RTL, natural keys navigation, * ordered/reversed, and all key presses (left, up, right, down). * * The test data is fairly straightforward and passes most configurations except for * special cases when using natural key navigation. For these cases, the test data * had to be 'inverted'. For example, the test data had to be inverted when the * slider has a horizontal orientation with either RTL or reversed option set (but not both). * * The test logic for sliders: * - Key to the left should change the value and lock to the tick to the left * - Key to the right should change the value and lock to the tick to the right * * For range sliders, the same logic is applied except test both handle1 and handle2. */ describe("`lock_to_ticks: true` keyboard navigation test cases", function() { var initialValue = 3; var initialRangeValues = [3, 7]; var tickValues = [0, 3, 5, 7, 10]; var stepValue = 0.1; var keyData = { left: 37, up: 38, right: 39, down: 40 }; var keys = ['left', 'up', 'right', 'down']; var orientations = ['horizontal', 'vertical']; var reversed = [false, true]; var naturalKeys = [false, true]; var ranged = [false, true]; var rtl = [false, true]; var rangeHandles = ['handle1', 'handle2']; var testCases = []; orientations.forEach(function(orientation) { ranged.forEach(function(isRange) { rtl.forEach(function(isRTL) { naturalKeys.forEach(function(isNatural) { reversed.forEach(function(isReversed) { testCases.push({ value: isRange ? initialRangeValues : initialValue, step: stepValue, range: isRange, orientation: orientation, reversed: isReversed, rtl: 'auto', natural_arrow_keys: isNatural, isRTL: isRTL, inputId: isRTL ? 'rtlSlider' : 'testSlider1', }); }); }); }); }); }); testCases.forEach(function(testCase) { var handles = testCase.range ? rangeHandles : ['handle1']; describe("orientation=" + testCase.orientation + ", range=" + testCase.range + ", rtl=" + testCase.isRTL + ", natural_arrow_keys=" + testCase.natural_arrow_keys + ", reversed=" + testCase.reversed, function() { var $testSlider; var $handle; var keyboardEvent; var sliderId; var sliderOptions; beforeEach(function() { sliderId = testCase.range ? 'myRangeSlider' : 'mySlider'; sliderOptions = { id: sliderId, step: testCase.step, orientation: testCase.orientation, value: testCase.value, range: testCase.range, lock_to_ticks: true, reversed: testCase.reversed, rtl: 'auto', natural_arrow_keys: testCase.natural_arrow_keys, ticks: tickValues, }; // Create keyboard event keyboardEvent = document.createEvent('Event'); keyboardEvent.initEvent('keydown', true, true); }); afterEach(function() { keyboardEvent = null; if ($testSlider) { $testSlider.slider('destroy'); $testSlider = null; } }); handles.forEach(function(handleKey) { keys.forEach(function(key) { var isHorizontal = testCase.orientation === 'horizontal'; var isVertical = testCase.orientation === 'vertical'; var isRange = testCase.range; var isRTL = testCase.isRTL; var isReversed = testCase.reversed; var isNatural = testCase.natural_arrow_keys; var testData = { keyCode: keyData[key], handle1: { expectedValue: null }, handle2: { expectedValue: null } }; if (isRange) { // If initial value is [3, 7] Then // These expected values will pass 96/128 tests (32 failures) if (key === 'left' || key === 'down') { testData.handle1.expectedValue = [0, 7]; testData.handle2.expectedValue = [3, 5]; } else if (key === 'right' || key === 'up') { testData.handle1.expectedValue = [5, 7]; testData.handle2.expectedValue = [3, 10]; } // Special cases when using natural keys if (isNatural) { if ((isHorizontal && isReversed && !isRTL) || (isHorizontal && isRTL && !isReversed) || (isVertical && !isReversed)) { if (key === 'left' || key === 'down') { testData.handle1.expectedValue = [5, 7]; testData.handle2.expectedValue = [3, 10]; } else if (key === 'right' || key === 'up') { testData.handle1.expectedValue = [0, 7]; testData.handle2.expectedValue = [3, 5]; } } } } else { // If initial value is 3 Then // These expected values will pass 48/64 tests (16 failures) if (key === 'left' || key === 'down') { testData.handle1.expectedValue = 0; } else if (key === 'right' || key === 'up') { testData.handle1.expectedValue = 5; } // Special cases when using natural keys if (isNatural) { // XOR // One or the other needs to be true if ((isHorizontal && isReversed && !isRTL) || (isHorizontal && isRTL && !isReversed) || (isVertical && !isReversed)) { if (key === 'left' || key === 'down') { testData.handle1.expectedValue = 5; } else if (key === 'right' || key === 'up') { testData.handle1.expectedValue = 0; } } } } it("Should lock to tick (" + handleKey + ", key=" + key + ")", function(done) { $testSlider = $('#'+testCase.inputId).slider(sliderOptions); $handle = $('#'+sliderId).find('.slider-handle:' + (handleKey === 'handle1' ? 'first' : 'last')); $handle.on('keydown', function() { var value = $testSlider.slider('getValue'); expect(value).toEqual(testData[handleKey].expectedValue); done(); }); keyboardEvent.keyCode = keyboardEvent.which = testData.keyCode; $handle[0].dispatchEvent(keyboardEvent); }); }); }); }); }); });