| <!DOCTYPE html> |
| <!-- |
| @license |
| Copyright (C) 2016 The Android Open Source Project |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| --> |
| |
| <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes"> |
| <meta charset="utf-8"> |
| <title>gr-cursor-manager</title> |
| |
| <script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script> |
| |
| <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script> |
| <script src="/components/wct-browser-legacy/browser.js"></script> |
| |
| <test-fixture id="basic"> |
| <template> |
| <gr-cursor-manager cursor-target-class="targeted"></gr-cursor-manager> |
| <ul> |
| <li>A</li> |
| <li>B</li> |
| <li>C</li> |
| <li>D</li> |
| </ul> |
| </template> |
| </test-fixture> |
| |
| <script type="module"> |
| import '../../../test/common-test-setup.js'; |
| import './gr-cursor-manager.js'; |
| suite('gr-cursor-manager tests', () => { |
| let sandbox; |
| let element; |
| let list; |
| |
| setup(() => { |
| sandbox = sinon.sandbox.create(); |
| const fixtureElements = fixture('basic'); |
| element = fixtureElements[0]; |
| list = fixtureElements[1]; |
| }); |
| |
| teardown(() => { |
| sandbox.restore(); |
| }); |
| |
| test('core cursor functionality', () => { |
| // The element is initialized into the proper state. |
| assert.isArray(element.stops); |
| assert.equal(element.stops.length, 0); |
| assert.equal(element.index, -1); |
| assert.isNotOk(element.target); |
| |
| // Initialize the cursor with its stops. |
| element.stops = list.querySelectorAll('li'); |
| |
| // It should have the stops but it should not be targeting any of them. |
| assert.isNotNull(element.stops); |
| assert.equal(element.stops.length, 4); |
| assert.equal(element.index, -1); |
| assert.isNotOk(element.target); |
| |
| // Select the third stop. |
| element.setCursor(list.children[2]); |
| |
| // It should update its internal state and update the element's class. |
| assert.equal(element.index, 2); |
| assert.equal(element.target, list.children[2]); |
| assert.isTrue(list.children[2].classList.contains('targeted')); |
| assert.isFalse(element.isAtStart()); |
| assert.isFalse(element.isAtEnd()); |
| |
| // Progress the cursor. |
| element.next(); |
| |
| // Confirm that the next stop is selected and that the previous stop is |
| // unselected. |
| assert.equal(element.index, 3); |
| assert.equal(element.target, list.children[3]); |
| assert.isTrue(element.isAtEnd()); |
| assert.isFalse(list.children[2].classList.contains('targeted')); |
| assert.isTrue(list.children[3].classList.contains('targeted')); |
| |
| // Progress the cursor. |
| element.next(); |
| |
| // We should still be at the end. |
| assert.equal(element.index, 3); |
| assert.equal(element.target, list.children[3]); |
| assert.isTrue(element.isAtEnd()); |
| |
| // Wind the cursor all the way back to the first stop. |
| element.previous(); |
| element.previous(); |
| element.previous(); |
| |
| // The element state should reflect the end of the list. |
| assert.equal(element.index, 0); |
| assert.equal(element.target, list.children[0]); |
| assert.isTrue(element.isAtStart()); |
| assert.isTrue(list.children[0].classList.contains('targeted')); |
| |
| const newLi = document.createElement('li'); |
| newLi.textContent = 'Z'; |
| list.insertBefore(newLi, list.children[0]); |
| element.stops = list.querySelectorAll('li'); |
| |
| assert.equal(element.index, 1); |
| |
| // De-select all targets. |
| element.unsetCursor(); |
| |
| // There should now be no cursor target. |
| assert.isFalse(list.children[1].classList.contains('targeted')); |
| assert.isNotOk(element.target); |
| assert.equal(element.index, -1); |
| }); |
| |
| test('next() goes to first element when no cursor is set', () => { |
| element.stops = list.querySelectorAll('li'); |
| element.next(); |
| |
| assert.equal(element.index, 0); |
| assert.equal(element.target, list.children[0]); |
| assert.isTrue(list.children[0].classList.contains('targeted')); |
| assert.isTrue(element.isAtStart()); |
| assert.isFalse(element.isAtEnd()); |
| }); |
| |
| test('next() goes to first element when no cursor is set', () => { |
| element.stops = list.querySelectorAll('li'); |
| element.previous(); |
| |
| const lastIndex = list.children.length - 1; |
| assert.equal(element.index, lastIndex); |
| assert.equal(element.target, list.children[lastIndex]); |
| assert.isTrue(list.children[lastIndex].classList.contains('targeted')); |
| assert.isFalse(element.isAtStart()); |
| assert.isTrue(element.isAtEnd()); |
| }); |
| |
| test('_moveCursor', () => { |
| // Initialize the cursor with its stops. |
| element.stops = list.querySelectorAll('li'); |
| // Select the first stop. |
| element.setCursor(list.children[0]); |
| const getTargetHeight = sinon.stub(); |
| |
| // Move the cursor without an optional get target height function. |
| element._moveCursor(1); |
| assert.isFalse(getTargetHeight.called); |
| |
| // Move the cursor with an optional get target height function. |
| element._moveCursor(1, null, getTargetHeight); |
| assert.isTrue(getTargetHeight.called); |
| }); |
| |
| test('_moveCursor from for invalid index does not check height', () => { |
| element.stops = []; |
| const getTargetHeight = sinon.stub(); |
| element._moveCursor(1, () => false, getTargetHeight); |
| assert.isFalse(getTargetHeight.called); |
| }); |
| |
| test('opt_noScroll', () => { |
| sandbox.stub(element, '_targetIsVisible', () => false); |
| const scrollStub = sandbox.stub(window, 'scrollTo'); |
| element.stops = list.querySelectorAll('li'); |
| element.scrollBehavior = 'keep-visible'; |
| |
| element.setCursorAtIndex(1, true); |
| assert.isFalse(scrollStub.called); |
| |
| element.setCursorAtIndex(2); |
| assert.isTrue(scrollStub.called); |
| }); |
| |
| test('_getNextindex', () => { |
| const isLetterB = function(row) { |
| return row.textContent === 'B'; |
| }; |
| element.stops = list.querySelectorAll('li'); |
| // Start cursor at the first stop. |
| element.setCursor(list.children[0]); |
| |
| // Move forward to meet the next condition. |
| assert.equal(element._getNextindex(1, isLetterB), 1); |
| element.index = 1; |
| |
| // Nothing else meets the condition, should be at last stop. |
| assert.equal(element._getNextindex(1, isLetterB), 3); |
| element.index = 3; |
| |
| // Should stay at last stop if try to proceed. |
| assert.equal(element._getNextindex(1, isLetterB), 3); |
| |
| // Go back to the previous condition met. Should be back at. |
| // stop 1. |
| assert.equal(element._getNextindex(-1, isLetterB), 1); |
| element.index = 1; |
| |
| // Go back. No more meet the condition. Should be at stop 0. |
| assert.equal(element._getNextindex(-1, isLetterB), 0); |
| }); |
| |
| test('focusOnMove prop', () => { |
| const listEls = list.querySelectorAll('li'); |
| for (let i = 0; i < listEls.length; i++) { |
| sandbox.spy(listEls[i], 'focus'); |
| } |
| element.stops = listEls; |
| element.setCursor(list.children[0]); |
| |
| element.focusOnMove = false; |
| element.next(); |
| assert.isFalse(element.target.focus.called); |
| |
| element.focusOnMove = true; |
| element.next(); |
| assert.isTrue(element.target.focus.called); |
| }); |
| |
| suite('_scrollToTarget', () => { |
| let scrollStub; |
| setup(() => { |
| element.stops = list.querySelectorAll('li'); |
| element.scrollBehavior = 'keep-visible'; |
| |
| // There is a target which has a targetNext |
| element.setCursor(list.children[0]); |
| element._moveCursor(1); |
| scrollStub = sandbox.stub(window, 'scrollTo'); |
| window.innerHeight = 60; |
| }); |
| |
| test('Called when top and bottom not visible', () => { |
| sandbox.stub(element, '_targetIsVisible').returns(false); |
| element._scrollToTarget(); |
| assert.isTrue(scrollStub.called); |
| }); |
| |
| test('Not called when top and bottom visible', () => { |
| sandbox.stub(element, '_targetIsVisible').returns(true); |
| element._scrollToTarget(); |
| assert.isFalse(scrollStub.called); |
| }); |
| |
| test('Called when top is visible, bottom is not, scroll is lower', () => { |
| const visibleStub = sandbox.stub(element, '_targetIsVisible', |
| () => visibleStub.callCount === 2); |
| sandbox.stub(element, '_getWindowDims').returns({ |
| scrollX: 123, |
| scrollY: 15, |
| innerHeight: 1000, |
| pageYOffset: 0, |
| }); |
| sandbox.stub(element, '_calculateScrollToValue').returns(20); |
| element._scrollToTarget(); |
| assert.isTrue(scrollStub.called); |
| assert.isTrue(scrollStub.calledWithExactly(123, 20)); |
| assert.equal(visibleStub.callCount, 2); |
| }); |
| |
| test('Called when top is visible, bottom not, scroll is higher', () => { |
| const visibleStub = sandbox.stub(element, '_targetIsVisible', |
| () => visibleStub.callCount === 2); |
| sandbox.stub(element, '_getWindowDims').returns({ |
| scrollX: 123, |
| scrollY: 25, |
| innerHeight: 1000, |
| pageYOffset: 0, |
| }); |
| sandbox.stub(element, '_calculateScrollToValue').returns(20); |
| element._scrollToTarget(); |
| assert.isFalse(scrollStub.called); |
| assert.equal(visibleStub.callCount, 2); |
| }); |
| |
| test('_calculateScrollToValue', () => { |
| sandbox.stub(element, '_getWindowDims').returns({ |
| scrollX: 123, |
| scrollY: 25, |
| innerHeight: 300, |
| pageYOffset: 0, |
| }); |
| assert.equal(element._calculateScrollToValue(1000, {offsetHeight: 10}), |
| 905); |
| }); |
| }); |
| }); |
| </script> |