// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License.txt in the project root for license information. // // /// /// module WinJSTests { "use strict"; var list, proxy, first, second, focused, invoked, currentMode, layoutToDataIndex, Key = WinJS.Utilities.Key; var ListView = WinJS.UI.ListView; var callbacks = []; var oldRequestAnimationFrame; var oldHasWinRT; var _oldMaxTimePerCreateContainers; var defaultPrevented; function mockRequestAnimationFrame() { window.requestAnimationFrame = function (callback) { callbacks.push(callback); return 0; }; } function forceRequestAnimationFrames() { while (callbacks.length) { callbacks.shift()(); } } function createKeyEvent(element, key, preventDefaultHandler?, stopPropagationHandler?, altKey?, ctrlKey?, shiftKey?) { return { keyCode: key, target: element, altKey: altKey, ctrlKey: ctrlKey, shiftKey: shiftKey, stopPropagation: function () { if (stopPropagationHandler) { stopPropagationHandler(); } }, preventDefault: function () { if (preventDefaultHandler) { preventDefaultHandler(); } } }; } function isEmptyArray(array) { LiveUnit.Assert.areEqual(array.length, 0); } function eventOnElement(element) { var rect = element.getBoundingClientRect(); // Simulate clicking the middle of the element return { target: element, clientX: (rect.left + rect.right) / 2, clientY: (rect.top + rect.bottom) / 2, defaultPrevented: false, preventDefault: function () { this.defaultPrevented = true; } }; } function createModeForTabTests(element) { var mode = new WinJS.UI._SelectionMode({ _element: element, _isZombie: function () { return true; }, _selection: { _getFocused: function () { return mode._focused; } }, _changeFocus: function (entity) { mode._focused = entity; if (entity.type === WinJS.UI.ObjectType.item) { mode.site._groupFocusCache.updateCache("" + Math.floor(entity.index / 10), "" + entity.index, entity.index); } mode.site.focus(); }, _groups: { groupFromItem: function (index) { return Math.floor(index / 10); }, group: function (index) { return { key: "" + index }; }, fromKey: function (key) { return { group: { startIndex: +key * 10 } }; }, length: function () { return 100; }, }, _lastFocusedElementInGroupTrack: { type: WinJS.UI.ObjectType.item, index: -1 }, _rtl: function () { return false; }, _supportsGroupHeaderKeyboarding: true }); mode.site._groupFocusCache = new WinJS.UI._GroupFocusCache(mode.site); mode.site.focus = function () { mode.site._hasKeyboardFocus = true; }; mode.site.unfocus = function () { mode.site._hasKeyboardFocus = false; }; mode.site._changeFocus({ type: WinJS.UI.ObjectType.item, index: 0 }); return mode; } function createMode() { var mode = new WinJS.UI._SelectionMode({ _groups: new WinJS.UI._NoGroups(this), _versionManager: new WinJS.UI._VersionManager(), _element: list, _viewport: list, _canvas: list, _canvasProxy: proxy, _cachedCount: 10, _isZombie: function () { return true; }, _selection: { focused: { type: WinJS.UI.ObjectType.item, index: 0 }, clear: function () { }, set: function () { }, _isIncluded: function () { return false; }, _getFocused: function () { return focused; }, _setFocused: function (f) { focused = f; }, getRanges: function () { return []; } }, _options: {}, keyboardFocusedItem: { type: WinJS.UI.ObjectType.item, index: 0 }, _lastFocusedElementInGroupTrack: { type: WinJS.UI.ObjectType.item, index: -1 }, _unsetFocusOnItem: function () { this.keyboardFocusedItem = -1; }, _setFocusOnItem: function (item) { this.keyboardFocusedItem = item; }, scrollPosition: 0, _getViewportLength: function () { return 350; }, _setupTabOrder: function () { }, addEventListener: function () { }, removeEventListener: function () { }, ensureVisible: function () { }, _rtl: function () { return false; }, _view: { getAdjacent: function (oldFocus, direction) { return mode.site._layout.getKeyboardNavigatedItem(oldFocus, null, direction); }, items: { _itemFrom: function (element) { while (element && element !== first && element !== second) { element = element.parentNode; } return element; }, index: function (element) { switch (this._itemFrom(element)) { case first: return 0; case second: return 1; } }, itemDataAt: function (index) { var element = index ? second : first; return { element: element, container: element, itemBox: element }; }, itemAt: function (index) { return this.itemDataAt(index).element; }, containerAt: function (index) { return this.itemDataAt(index).container; }, itemBoxAt: function (index) { return this.itemDataAt(index).itemBox; } }, lastItemIndex: function () { return 8; } }, _changeFocus: function (newFocus) { focused = newFocus; this.keyboardFocusedItem = newFocus; }, _selectOnTap: function () { return false; }, _selectionAllowed: function () { return false; }, _selectFocused: function () { return false; }, _renderSelection: function () { }, _getItemPosition: function (index) { return { then: function (callback) { callback({ left: 0, top: 0, contentWidth: 100, contentHeight: 100 }); } }; }, _getItemOffset: function (index) { return { then: function (callback) { callback({ begin: 0, end: 100 }); } }; }, _convertFromCanvasCoordinates: function (range) { return range; }, _batchViewUpdates: function (stage, priority, functor) { functor(); }, _groupsEnabled: function () { return false; } }); mode._fireInvokeEvent = function (entity) { invoked = entity; }; return mode; } // As a side effect, this will scroll the browser to make the element visible function createPointerUpEvent(element) { element.scrollIntoView(false); var rect = element.getBoundingClientRect(); // Simulate clicking the middle of the element return { target: element, clientX: (rect.left + rect.right) / 2, clientY: (rect.top + rect.bottom) / 2, defaultPrevented: false, preventDefault: function () { this.defaultPrevented = true; }, button: undefined }; } export class BrowseModeTests { // This is the setup function that will be called at the beginning of each test function. setUp() { LiveUnit.LoggingCore.logComment("In setup"); var newNode = document.createElement("div"); newNode.id = "BrowseModeTests"; newNode.innerHTML = "
" + "
" + "
" + "
Label
" + "
" + "
" + "
Label
" + "
" + "
"; document.body.appendChild(newNode); list = document.getElementById("list"); proxy = document.getElementById("proxy"); first = document.getElementById("first"); second = document.getElementById("second"); proxy.setPointerCapture = function () { }; proxy.releasePointerCapture = function () { }; list.setPointerCapture = function () { }; list.releasePointerCapture = function () { }; callbacks = []; oldRequestAnimationFrame = window.requestAnimationFrame; oldHasWinRT = WinJS.Utilities.hasWinRT; WinJS.Utilities._setHasWinRT(false); focused = { type: WinJS.UI.ObjectType.item, index: 0 }; invoked = { type: WinJS.UI.ObjectType.item, index: WinJS.UI._INVALID_INDEX }; currentMode = null; layoutToDataIndex = []; defaultPrevented = false; //WinBlue: 298587 _oldMaxTimePerCreateContainers = WinJS.UI._VirtualizeContentsView._maxTimePerCreateContainers; WinJS.UI._VirtualizeContentsView._maxTimePerCreateContainers = Number.MAX_VALUE; } tearDown() { LiveUnit.LoggingCore.logComment("In tearDown"); WinJS.UI._VirtualizeContentsView._maxTimePerCreateContainers = _oldMaxTimePerCreateContainers; window.requestAnimationFrame = oldRequestAnimationFrame; WinJS.Utilities._setHasWinRT(oldHasWinRT); var element = document.getElementById("BrowseModeTests"); document.body.removeChild(element); } // Test methods // Any child objects that start with "test" are automatically test methods testSimpleClick = function (complete) { LiveUnit.LoggingCore.logComment("In testSimpleClick"); mockRequestAnimationFrame(); var browseMode = createMode(); invoked = { type: WinJS.UI.ObjectType.item, index: WinJS.UI._INVALID_INDEX }; LiveUnit.Assert.isTrue(!WinJS.Utilities.hasClass(first, WinJS.UI._pressedClass)); browseMode.onPointerDown({ target: first, button: WinJS.UI._LEFT_MSPOINTER_BUTTON, preventDefault: function () { } }); forceRequestAnimationFrames(); LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(first, WinJS.UI._pressedClass)); browseMode.onPointerUp(createPointerUpEvent(first)); browseMode.onclick(); WinJS.Utilities._setImmediate(function () { LiveUnit.Assert.areEqual(0, invoked.index); LiveUnit.Assert.isTrue(!WinJS.Utilities.hasClass(first, WinJS.UI._pressedClass)); complete(); }); }; testInvokeEvent = function (complete) { var items = []; for (var i = 0; i < 100; ++i) { items[i] = { title: "Tile" + i }; } var listView = new ListView(list, { itemDataSource: new WinJS.Binding.List(items).dataSource }); Helper.ListView.waitForReady(listView, -1)().then(function () { var gotItemInvokedEvent = false; listView.addEventListener("iteminvoked", function () { gotItemInvokedEvent = true; }); listView.tapBehavior = WinJS.UI.TapBehavior.none; listView.selectionMode = WinJS.UI.SelectionMode.none; var firstItem = listView.elementFromIndex(0); listView._mode.onKeyDown(createKeyEvent(firstItem, Key.enter)); LiveUnit.Assert.isFalse(gotItemInvokedEvent); listView.tapBehavior = WinJS.UI.TapBehavior.invokeOnly; listView._mode.onKeyDown(createKeyEvent(firstItem, Key.enter)); LiveUnit.Assert.isTrue(gotItemInvokedEvent); gotItemInvokedEvent = false; listView.tapBehavior = WinJS.UI.TapBehavior.toggleSelect; listView.selectionMode = WinJS.UI.SelectionMode.single; listView._mode.onKeyDown(createKeyEvent(firstItem, Key.enter)); LiveUnit.Assert.isTrue(gotItemInvokedEvent); gotItemInvokedEvent = false; listView.selectionMode = WinJS.UI.SelectionMode.multi; listView._mode.onKeyDown(createKeyEvent(firstItem, Key.enter)); LiveUnit.Assert.isFalse(gotItemInvokedEvent); complete(); }); }; // Verify that right-click doesn't trigger the pressed visual when selection is disabled testRightClickDisabled = function (complete) { LiveUnit.LoggingCore.logComment("In testRightClickDisabled"); mockRequestAnimationFrame(); var browseMode = createMode(); browseMode.site._selectionMode = "none"; LiveUnit.Assert.isFalse(WinJS.Utilities.hasClass(first, WinJS.UI._pressedClass)); browseMode.onPointerDown({ target: first, button: WinJS.UI._RIGHT_MSPOINTER_BUTTON, preventDefault: function () { } }); forceRequestAnimationFrames(); LiveUnit.Assert.isFalse(WinJS.Utilities.hasClass(first, WinJS.UI._pressedClass)); browseMode.onPointerUp(createPointerUpEvent(first)); browseMode.onclick(); WinJS.Utilities._setImmediate(function () { LiveUnit.Assert.isFalse(WinJS.Utilities.hasClass(first, WinJS.UI._pressedClass)); complete(); }); }; testDownMoveUp = function (complete) { LiveUnit.LoggingCore.logComment("In testDownMoveUp"); mockRequestAnimationFrame(); var browseMode = createMode(); invoked = { type: WinJS.UI.ObjectType.item, index: WinJS.UI._INVALID_INDEX }; LiveUnit.Assert.isTrue(!WinJS.Utilities.hasClass(first, WinJS.UI._pressedClass)); LiveUnit.Assert.isTrue(!WinJS.Utilities.hasClass(second, WinJS.UI._pressedClass)); browseMode.onPointerDown({ target: first, button: WinJS.UI._LEFT_MSPOINTER_BUTTON, preventDefault: function () { } }); forceRequestAnimationFrames(); LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(first, WinJS.UI._pressedClass)); LiveUnit.Assert.areEqual(WinJS.UI._INVALID_INDEX, invoked.index); browseMode.onPointerUp(createPointerUpEvent(second)); browseMode.onclick(); WinJS.Utilities._setImmediate(function () { LiveUnit.Assert.isTrue(!WinJS.Utilities.hasClass(first, WinJS.UI._pressedClass)); LiveUnit.Assert.isTrue(!WinJS.Utilities.hasClass(second, WinJS.UI._pressedClass)); complete(); }); }; testMoveUp = function () { LiveUnit.LoggingCore.logComment("In testMoveUp"); var browseMode = createMode(); invoked = { type: WinJS.UI.ObjectType.item, index: WinJS.UI._INVALID_INDEX }; LiveUnit.Assert.isTrue(!WinJS.Utilities.hasClass(first, WinJS.UI._pressedClass)); LiveUnit.Assert.isTrue(!WinJS.Utilities.hasClass(second, WinJS.UI._pressedClass)); LiveUnit.Assert.isTrue(!WinJS.Utilities.hasClass(first, WinJS.UI._pressedClass)); LiveUnit.Assert.isTrue(!WinJS.Utilities.hasClass(second, WinJS.UI._pressedClass)); LiveUnit.Assert.areEqual(WinJS.UI._INVALID_INDEX, invoked.index); browseMode.onPointerUp(createPointerUpEvent(second)); browseMode.onclick(); LiveUnit.Assert.areEqual(WinJS.UI._INVALID_INDEX, invoked.index); LiveUnit.Assert.isTrue(!WinJS.Utilities.hasClass(first, WinJS.UI._pressedClass)); LiveUnit.Assert.isTrue(!WinJS.Utilities.hasClass(second, WinJS.UI._pressedClass)); }; testDownMoveBackUp = function (complete) { LiveUnit.LoggingCore.logComment("In testDownMoveBackUp"); mockRequestAnimationFrame(); var browseMode = createMode(); invoked = { type: WinJS.UI.ObjectType.item, index: WinJS.UI._INVALID_INDEX }; LiveUnit.Assert.isTrue(!WinJS.Utilities.hasClass(first, WinJS.UI._pressedClass)); LiveUnit.Assert.isTrue(!WinJS.Utilities.hasClass(second, WinJS.UI._pressedClass)); browseMode.onPointerDown({ target: first, button: WinJS.UI._LEFT_MSPOINTER_BUTTON, preventDefault: function () { } }); forceRequestAnimationFrames(); LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(first, WinJS.UI._pressedClass)); LiveUnit.Assert.areEqual(WinJS.UI._INVALID_INDEX, invoked.index); LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(first, WinJS.UI._pressedClass)); LiveUnit.Assert.isTrue(!WinJS.Utilities.hasClass(second, WinJS.UI._pressedClass)); LiveUnit.Assert.areEqual(WinJS.UI._INVALID_INDEX, invoked.index); browseMode.onPointerUp(createPointerUpEvent(first)); browseMode.onclick(); WinJS.Utilities._setImmediate(function () { LiveUnit.Assert.areEqual(0, invoked.index); LiveUnit.Assert.isTrue(!WinJS.Utilities.hasClass(first, WinJS.UI._pressedClass)); LiveUnit.Assert.isTrue(!WinJS.Utilities.hasClass(second, WinJS.UI._pressedClass)); complete(); }); }; testKeyboard = function () { var Key = WinJS.Utilities.Key; var browseMode = createMode(); // We're pretending to be a 3x3 horizontal grid here browseMode.site._layout = { getKeyboardNavigatedItem: function (entity, element, keyPressed) { if (keyPressed === WinJS.Utilities.Key.upArrow) { return WinJS.Promise.wrap({ type: WinJS.UI.ObjectType.item, index: entity.index - 1 }); } else if (keyPressed === WinJS.Utilities.Key.downArrow) { return WinJS.Promise.wrap({ type: WinJS.UI.ObjectType.item, index: entity.index + 1 }); } else if (keyPressed === WinJS.Utilities.Key.leftArrow) { return WinJS.Promise.wrap({ type: WinJS.UI.ObjectType.item, index: entity.index - 3 }); } else if (keyPressed === WinJS.Utilities.Key.rightArrow) { return WinJS.Promise.wrap({ type: WinJS.UI.ObjectType.item, index: entity.index + 3 }); } else if (keyPressed === WinJS.Utilities.Key.pageUp) { return WinJS.Promise.wrap({ type: WinJS.UI.ObjectType.item, index: 0 }); } else if (keyPressed === WinJS.Utilities.Key.pageDown) { return WinJS.Promise.wrap({ type: WinJS.UI.ObjectType.item, index: 8 }); } } }; var ensureVisibleCalled = false; browseMode.site.ensureVisible = function (index) { ensureVisibleCalled = true; } function createKeyEvent(key) { return { keyCode: key, target: first, stopPropagation: function () { }, preventDefault: function () { } }; } browseMode.onKeyDown(createKeyEvent(Key.upArrow)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 0); browseMode.onKeyDown(createKeyEvent(Key.downArrow)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 1); browseMode.onKeyDown(createKeyEvent(Key.downArrow)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 2); browseMode.onKeyDown(createKeyEvent(Key.upArrow)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 1); browseMode.onKeyDown(createKeyEvent(Key.leftArrow)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 1); browseMode.onKeyDown(createKeyEvent(Key.rightArrow)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 4); browseMode.onKeyDown(createKeyEvent(Key.downArrow)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 5); browseMode.onKeyDown(createKeyEvent(Key.downArrow)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 6); browseMode.onKeyDown(createKeyEvent(Key.downArrow)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 7); browseMode.onKeyDown(createKeyEvent(Key.downArrow)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 8); ensureVisibleCalled = false; browseMode.onKeyDown(createKeyEvent(Key.downArrow)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 8); browseMode.onKeyDown(createKeyEvent(Key.rightArrow)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 8); browseMode.onKeyDown(createKeyEvent(Key.rightArrow)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 8); browseMode.onKeyDown(createKeyEvent(Key.home)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 0); ensureVisibleCalled = false; browseMode.onKeyDown(createKeyEvent(Key.home)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 0); browseMode.onKeyDown(createKeyEvent(Key.end)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 8); ensureVisibleCalled = false; browseMode.onKeyDown(createKeyEvent(Key.end)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 8); browseMode.onKeyDown(createKeyEvent(Key.pageDown)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 8); ensureVisibleCalled = false; browseMode.onKeyDown(createKeyEvent(Key.pageDown)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 8); browseMode.onKeyDown(createKeyEvent(Key.leftArrow)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 5); browseMode.onKeyDown(createKeyEvent(Key.pageDown)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 8); browseMode.onKeyDown(createKeyEvent(Key.pageUp)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 0); invoked = { type: WinJS.UI.ObjectType.item, index: WinJS.UI._INVALID_INDEX }; browseMode.onKeyDown(createKeyEvent(Key.end)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 8); browseMode.onKeyDown(createKeyEvent(Key.enter)); LiveUnit.Assert.areEqual(invoked.index, 8); browseMode.onKeyDown(createKeyEvent(Key.home)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 0); function onKeyboardNavigating_handler(event) { if (event.detail.newFocus > 4) { event.preventDefault(); } } document.body.addEventListener("keyboardnavigating", onKeyboardNavigating_handler); browseMode.onKeyDown(createKeyEvent(Key.end)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 0); browseMode.onKeyDown(createKeyEvent(Key.rightArrow)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 3); browseMode.onKeyDown(createKeyEvent(Key.rightArrow)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 3); browseMode.onKeyDown(createKeyEvent(Key.downArrow)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 4); browseMode.onKeyDown(createKeyEvent(Key.downArrow)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 4); browseMode.onKeyDown(createKeyEvent(Key.pageDown)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 4); browseMode.onKeyDown(createKeyEvent(Key.pageUp)); LiveUnit.Assert.areEqual(browseMode.site.keyboardFocusedItem.index, 0); document.body.removeEventListener("keyboardnavigating", onKeyboardNavigating_handler); } testInvocableHeaders = function (complete) { var host = document.querySelector("#list"); host.style.position = "absolute"; //this test requires the element not to scroll host.style.top = "0"; var data = []; for (var i = 0; i < 10; i++) { data.push({ data: i + "" }); } var list = new WinJS.Binding.List(data); var glist = list.createGrouped(function (item) { return Math.floor(item.data / 2) + ""; }, function (item) { return { data: Math.floor(item.data / 2) + "" }; }); var lv = new ListView(host); lv.itemTemplate = lv.groupHeaderTemplate = function (itemPromise) { return itemPromise.then(function (item) { var div = document.createElement("div"); div.textContent = item.data.data; div.style.width = "50px"; div.style.height = "50px"; div.style.backgroundColor = "red"; return div; }); }; lv.itemDataSource = glist.dataSource; lv.groupDataSource = glist.groups.dataSource; var numInvokes = 0; var gotEventFromOnEvent = 0; lv.ongroupheaderinvoked = function (e) { LiveUnit.Assert.areEqual(e.detail.groupHeaderIndex, 2); if (++gotEventFromOnEvent === 2 && numInvokes === 2) { complete(); } }; lv.addEventListener("groupheaderinvoked", function (e) { LiveUnit.Assert.areEqual(e.detail.groupHeaderIndex, 2); if (++numInvokes === 2 && gotEventFromOnEvent === 2) { complete(); } }); lv.indexOfFirstVisible = 4; //ensure group 2 is visible on the screen Helper.ListView.waitForReady(lv, -1)().then(function () { var header = lv.element.querySelectorAll(".win-groupheader")[2]; lv._changeFocus({ type: WinJS.UI.ObjectType.groupHeader, index: 2 }, true, false, false, true); lv._mode.onKeyDown({ target: header, keyCode: 13, stopPropagation: function () { }, preventDefault: function () { } }); var e = createPointerUpEvent(header); e.button = WinJS.UI._LEFT_MSPOINTER_BUTTON; lv._mode.onPointerDown(e); lv._mode.onPointerUp(e); }); }; testTabbingToHeaderFromChildElementDrawsFocusRect = function (complete) { var data = []; for (var i = 0; i < 20; i++) { data.push({ data: i }); } var list = new WinJS.Binding.List(data); var glist = list.createGrouped(function (item) { return Math.floor(item.data / 2) + ""; }, function (item) { return { data: Math.floor(item.data / 2) + "" }; }); var lv = new ListView(); lv.layout['groupHeaderPosition'] = "left"; lv.itemDataSource = glist.dataSource; lv.groupDataSource = glist.groups.dataSource; lv.groupHeaderTemplate = function (itemPromise) { return itemPromise.then(function (item) { var div = document.createElement("div"); div.style.width = "200px"; div.style.height = "200px"; var innerDiv = document.createElement("div"); innerDiv.innerHTML = item.data.data; var button = document.createElement("input"); button.classList.add("testHeaderButtonClass"); button.type = "button"; button.value = "button"; div.appendChild(innerDiv); div.appendChild(button); return div; }); }; document.body.appendChild(lv.element); var headerButton; Helper.ListView.waitForReady(lv, -1)().then(function () { headerButton = document.querySelector(".testHeaderButtonClass"); headerButton.focus(); return Helper.ListView.waitForReady(lv, -1)(); }).then(function () { lv._keyboardFocusInbound = true; headerButton.parentNode.focus(); return Helper.ListView.waitForReady(lv, -1)(); }).done(function () { LiveUnit.Assert.isTrue(headerButton.parentNode.classList.contains(WinJS.UI._itemFocusClass)); document.body.removeChild(lv.element); complete(); }); }; testPointerDownOnHeaderAndUpOnItemShouldNotInvoke = function (complete) { var host = document.querySelector("#list"); host.style.position = "absolute"; //this test requires the element not to scroll host.style.top = "0"; var data = []; for (var i = 0; i < 10; i++) { data.push({ data: i + "" }); } var list = new WinJS.Binding.List(data); var glist = list.createGrouped(function (item) { return Math.floor(item.data / 2) + ""; }, function (item) { return { data: Math.floor(item.data / 2) + "" }; }); var lv = new ListView(host); lv.itemTemplate = lv.groupHeaderTemplate = function (itemPromise) { return itemPromise.then(function (item) { var div = document.createElement("div"); div.textContent = item.data.data; div.style.width = "50px"; div.style.height = "50px"; div.style.backgroundColor = "red"; return div; }); }; lv.itemDataSource = glist.dataSource; lv.groupDataSource = glist.groups.dataSource; lv.addEventListener("groupheaderinvoked", function (e) { LiveUnit.Assert.fail("group header should not be invoked"); }); lv.indexOfFirstVisible = 4; //ensure group 2 is visible on the screen Helper.ListView.waitForReady(lv, -1)().then(function () { var header = lv.element.querySelector(".win-groupheader"); var item = lv.element.querySelector(".win-item"); var e = createPointerUpEvent(header); e.button = WinJS.UI._LEFT_MSPOINTER_BUTTON; lv._mode.onPointerDown(e); e = createPointerUpEvent(item); e.button = WinJS.UI._LEFT_MSPOINTER_BUTTON; lv._mode.onPointerUp(e); return Helper.ListView.waitForReady(lv, -1)(); }).done(function () { complete(); }); }; testOnTabExiting = function () { // This test mode mimics a ListView with infinite number of groups, // where each group has 10 items: group0 has items 0-9, group1 has items 10-19, etc var prevent = false; var element = document.createElement("div"); element.addEventListener("keyboardnavigating", function (e) { if (prevent) { e.preventDefault(); } }); var mode = createModeForTabTests(element); mode.site._changeFocus({ type: WinJS.UI.ObjectType.item, index: 11 }); // Tab forward from item11, but prevent navigation prevent = true; mode.onTabExiting({ detail: 1, preventDefault: function () { } }); LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.item, mode.site._selection._getFocused().type); LiveUnit.Assert.areEqual(11, mode.site._selection._getFocused().index); prevent = false; // Tab forward from item11, should go to group1 mode.onTabExiting({ detail: 1, preventDefault: function () { } }); LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.groupHeader, mode.site._selection._getFocused().type); LiveUnit.Assert.areEqual(1, mode.site._selection._getFocused().index); // Tab backwards from group1, but prevent navigation prevent = true; mode.onTabExiting({ detail: 0, preventDefault: function () { } }); LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.groupHeader, mode.site._selection._getFocused().type); LiveUnit.Assert.areEqual(1, mode.site._selection._getFocused().index); prevent = false; // Tab backwards from group1, should go to item11 mode.onTabExiting({ detail: 0, preventDefault: function () { } }); LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.item, mode.site._selection._getFocused().type); LiveUnit.Assert.areEqual(11, mode.site._selection._getFocused().index); // Tab backwards from item11, leaving listView, should not change our internal focus state mode.onTabExiting({ detail: 0, preventDefault: function () { } }); LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.item, mode.site._selection._getFocused().type); LiveUnit.Assert.areEqual(11, mode.site._selection._getFocused().index); // Tab forward from group1, leaving listView, should not change our internal focus state mode.site._changeFocus({ type: WinJS.UI.ObjectType.groupHeader, index: 1 }); mode.onTabExiting({ detail: 1, preventDefault: function () { } }); LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.groupHeader, mode.site._selection._getFocused().type); LiveUnit.Assert.areEqual(1, mode.site._selection._getFocused().index); }; testOnTabEntered = function () { // This test mode mimics a ListView with infinite number of groups, // where each group has 10 items: group0 has items 0-9, group1 has items 10-19, etc var fireCount = 0; var element = document.createElement("div"); element.addEventListener("keyboardnavigating", function (e) { fireCount++; }); var mode = createModeForTabTests(element); mode.site._changeFocus({ type: WinJS.UI.ObjectType.item, index: 11 }); mode.site.unfocus(); // Tab forward into the listView, focus should go to item11 var curFireCount = fireCount; mode.onTabEntered({ detail: 1, preventDefault: function () { } }); LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.item, mode.site._selection._getFocused().type); LiveUnit.Assert.areEqual(11, mode.site._selection._getFocused().index); LiveUnit.Assert.areEqual(curFireCount, fireCount); mode.site._changeFocus({ type: WinJS.UI.ObjectType.groupHeader, index: 1 }); mode.site.unfocus(); // Tab forward into the listView, focus should go to item11 // even though the last focused entity was a header curFireCount = fireCount; mode.onTabEntered({ detail: 1, preventDefault: function () { } }); LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.item, mode.site._selection._getFocused().type); LiveUnit.Assert.areEqual(11, mode.site._selection._getFocused().index); LiveUnit.Assert.areEqual(curFireCount + 1, fireCount); mode.site._changeFocus({ type: WinJS.UI.ObjectType.groupHeader, index: 1 }); mode.site.unfocus(); // Tab backwards into the listView, focus should go to header1 curFireCount = fireCount; mode.onTabEntered({ detail: 0, preventDefault: function () { } }); LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.groupHeader, mode.site._selection._getFocused().type); LiveUnit.Assert.areEqual(1, mode.site._selection._getFocused().index); LiveUnit.Assert.areEqual(curFireCount, fireCount); mode.site._changeFocus({ type: WinJS.UI.ObjectType.item, index: 11 }); mode.site.unfocus(); // Tab backwards into the listView, focus should go to header1 // even though the last focused entity was an item curFireCount = fireCount; mode.onTabEntered({ detail: 0, preventDefault: function () { } }); LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.groupHeader, mode.site._selection._getFocused().type); LiveUnit.Assert.areEqual(1, mode.site._selection._getFocused().index); LiveUnit.Assert.areEqual(curFireCount + 1, fireCount); }; testResetPointerStateAfterPressingEnterOnHeader = function (complete) { Helper.initUnhandledErrors(); var data = []; for (var i = 0; i < 10; i++) { data.push({ data: i + "" }); } var list = new WinJS.Binding.List(data); var glist = list.createGrouped(function (item) { return Math.floor(item.data / 2) + ""; }, function (item) { return { data: Math.floor(item.data / 2) + "" }; }); var lv = new ListView(document.querySelector("#list")); lv.itemDataSource = glist.dataSource; lv.groupDataSource = glist.groups.dataSource; Helper.ListView.waitForReady(lv, -1)(). then(function () { lv.currentItem = { type: WinJS.UI.ObjectType.groupHeader, index: 0 }; lv._mode.onKeyDown(createKeyEvent(document.querySelector(".win-groupheader"), Key.enter)); lv._mode._itemEventsHandler.resetPointerDownState(); return Helper.ListView.waitForReady(lv, -1)(); }). then(Helper.validateUnhandledErrorsOnIdle). done(complete); }; testKeyboardingBeforeTreeCreated = function (complete) { Helper.initUnhandledErrors(); var placeholder = document.createElement("div"); placeholder.style.width = "300px"; placeholder.style.height = "300px"; document.body.appendChild(placeholder); var items = []; for (var i = 0; i < 100; ++i) { items[i] = { title: "Tile" + i }; } var list = new WinJS.Binding.List(items); var layout = { initialize: function (site, groups) { } }; var layouSignal = new WinJS._Signal(); Object.defineProperty(layout, "numberOfItemsPerItemsBlock", { get: function () { return layouSignal.promise; } }); var listView = new ListView(placeholder, { itemDataSource: list.dataSource, layout: layout }); listView._mode.onKeyDown(createKeyEvent(listView._canvas, Key.end)); listView._mode.onKeyDown(createKeyEvent(listView._canvas, Key.home)); listView._mode.onKeyDown(createKeyEvent(listView._canvas, Key.leftArrow)); listView._mode.onKeyDown(createKeyEvent(listView._canvas, Key.rightArrow)); listView._mode.onKeyDown(createKeyEvent(listView._canvas, Key.upArrow)); listView._mode.onKeyDown(createKeyEvent(listView._canvas, Key.downArrow)); listView._mode.onKeyDown(createKeyEvent(listView._canvas, Key.pageUp)); listView._mode.onKeyDown(createKeyEvent(listView._canvas, Key.pageDown)); listView._mode.onKeyDown(createKeyEvent(listView._canvas, Key.enter)); layouSignal.complete(10); Helper.ListView.waitForReady(listView)(). then(Helper.validateUnhandledErrorsOnIdle). done(function () { document.body.removeChild(placeholder); complete(); }); }; testKeyboardReorderStopsPropagationOnArrowKeyUp = function (complete) { if (!WinJS.Utilities.isPhone) { return complete(); } var placeholder = document.createElement("div"); placeholder.style.width = "300px"; placeholder.style.height = "300px"; document.body.appendChild(placeholder); var items = []; for (var i = 0; i < 100; ++i) { items[i] = { title: "Tile" + i }; } var list = new WinJS.Binding.List(items); var listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemsReorderable: true }); var count = 0; function incCount() { count++; } Helper.ListView.waitForReady(listView, -1)().done(function () { listView._mode.onKeyDown(createKeyEvent(listView._canvas, Key.alt)); listView._mode.onKeyDown(createKeyEvent(listView._canvas, Key.shift, null, null, true)); listView._mode.onKeyDown(createKeyEvent(listView._canvas, Key.leftArrow, null, null, true, false, true)); listView._mode.onKeyUp(createKeyEvent(listView._canvas, Key.leftArrow, incCount, incCount, true, false, true)); LiveUnit.Assert.areEqual(2, count); listView.dispose(); document.body.removeChild(placeholder); complete(); }); }; testKeyboardReorderStopsPropagationOnShiftThenArrowKeyUp = function (complete) { if (!WinJS.Utilities.isPhone) { return complete(); } var placeholder = document.createElement("div"); placeholder.style.width = "300px"; placeholder.style.height = "300px"; document.body.appendChild(placeholder); var items = []; for (var i = 0; i < 100; ++i) { items[i] = { title: "Tile" + i }; } var list = new WinJS.Binding.List(items); var listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemsReorderable: true }); var count = 0; function incCount() { count++; } Helper.ListView.waitForReady(listView, -1)().done(function () { listView._mode.onKeyDown(createKeyEvent(listView._canvas, Key.alt)); listView._mode.onKeyDown(createKeyEvent(listView._canvas, Key.shift, null, null, true)); listView._mode.onKeyDown(createKeyEvent(listView._canvas, Key.leftArrow, null, null, true, false, true)); listView._mode.onKeyUp(createKeyEvent(listView._canvas, Key.shift, null, null, true)); listView._mode.onKeyUp(createKeyEvent(listView._canvas, Key.leftArrow, incCount, incCount, true)); LiveUnit.Assert.areEqual(2, count); listView.dispose(); document.body.removeChild(placeholder); complete(); }); }; }; function generateChangeDataSourceInInvoke(layoutName) { BrowseModeTests.prototype["testChangeDataSourceInInvoke" + layoutName] = function (complete) { var bindingList1 = new WinJS.Binding.List(["a", "b", "c", "d"]); var listView = new ListView(list, { itemDataSource: bindingList1.dataSource, layout: new WinJS.UI[layoutName](), itemTemplate: function (itemPromise) { var element = document.createElement("div"); element.style.width = "100"; element.style.height = "100"; element.style.backgroundColor = "#777"; return { element: element, renderComplete: itemPromise.then(function (item) { element.textContent = '' + item.data; }) }; } }); listView._canvas.setPointerCapture = function () { }; var tests = [function () { var elements = list.querySelectorAll('.win-container'); LiveUnit.Assert.areEqual(4, elements.length); // Click on the 4th item and then switch to a data source with 3 items listView._currentMode().onPointerDown({ target: elements[3], button: WinJS.UI._LEFT_MSPOINTER_BUTTON, preventDefault: function () { } }); listView._currentMode().onPointerUp(createPointerUpEvent(elements[3])); listView._currentMode().onclick(); }, function () { var elements = list.querySelectorAll('.win-container'); LiveUnit.Assert.areEqual(3, elements.length); // Press enter on the 3rd item and then switch to a data source with 2 items listView._selection._setFocused({ type: WinJS.UI.ObjectType.item, index: 2 }, true); listView._currentMode().onKeyDown({ target: elements[2], preventDefault: function () { }, stopPropagation: function () { }, keyCode: WinJS.Utilities.Key.enter }); }, function () { var elements = list.querySelectorAll('.win-container'); LiveUnit.Assert.areEqual(2, elements.length); complete(); }]; var i = 0; listView.addEventListener('iteminvoked', function () { if (i === 0) { var bindingList2 = new WinJS.Binding.List(["x", "y", "z"]); listView.itemDataSource = bindingList2.dataSource; i++; } else { var bindingList2 = new WinJS.Binding.List(["first", "second"]); listView.itemDataSource = bindingList2.dataSource; } }); Helper.ListView.runTests(listView, tests); }; }; generateChangeDataSourceInInvoke("GridLayout"); function generateInvokeOnUnrealizedItem(layoutName) { BrowseModeTests.prototype["testInvokeOnUnrealizedItem" + layoutName] = function (complete) { var data = []; for (var i = 0; i < 1000; i++) { data.push("Item" + i); } var bindingList = new WinJS.Binding.List(data); var listView = new ListView(list, { itemDataSource: bindingList.dataSource, layout: new WinJS.UI[layoutName](), itemTemplate: function (itemPromise) { var element = document.createElement("div"); element.style.width = "100px"; element.style.height = "100px"; return { element: element, renderComplete: itemPromise.then(function (item) { element.textContent = item.data; }) }; }, currentItem: { index: 4, hasFocus: true } }); listView._canvas.setPointerCapture = function () { }; var expectingInvokeEvent = false, gotInvokeEvent = false, targetElement = null; listView.addEventListener('iteminvoked', function (e) { LiveUnit.Assert.isTrue(expectingInvokeEvent); gotInvokeEvent = true; if (expectingInvokeEvent) { expectingInvokeEvent = false; } }); Helper.ListView.waitForDeferredAction(listView)().then(function () { targetElement = listView._tabManager.childFocus; expectingInvokeEvent = true; LiveUnit.Assert.areEqual("Item4", targetElement.textContent); listView._currentMode().onKeyDown({ target: targetElement, preventDefault: function () { }, stopPropagation: function () { }, keyCode: WinJS.Utilities.Key.enter }); return WinJS.Promise.timeout(); }).then(function () { LiveUnit.Assert.isTrue(targetElement === listView._tabManager.childFocus); LiveUnit.Assert.isTrue(gotInvokeEvent); gotInvokeEvent = false; var scrollPos = WinJS.Utilities.getScrollPosition(listView._viewport); scrollPos.scrollLeft += 5000; WinJS.Utilities.setScrollPosition(listView._viewport, scrollPos); return Helper.ListView.waitForDeferredAction(listView)(); }).then(function () { LiveUnit.Assert.isFalse(listView.element.contains(targetElement)); LiveUnit.Assert.isTrue(targetElement !== listView._tabManager.childFocus); listView._currentMode().onKeyDown({ target: targetElement, preventDefault: function () { }, stopPropagation: function () { }, keyCode: WinJS.Utilities.Key.enter }); // Give the test some time to see if it fires an invoke event on enter. It shouldn't fire an invoke event here. setTimeout(function () { LiveUnit.Assert.isFalse(gotInvokeEvent); complete(); }, 500); }); }; }; generateInvokeOnUnrealizedItem("GridLayout"); function generateEnsureVisibleNegative(layoutName) { BrowseModeTests.prototype["testEnsureVisibleNegative" + layoutName] = function (complete) { var data = []; for (var i = 0; i < 1000; i++) { data.push("Item" + i); } var bindingList = new WinJS.Binding.List(data); var listView = new WinJS.UI.ListView(list, { itemDataSource: bindingList.dataSource, layout: new WinJS.UI[layoutName](), }); Helper.initUnhandledErrors(); var range; var promiseErrorsTimeout = setTimeout(function () { Helper.validateUnhandledErrors(); }, 9900); var ensureVisibleIndices = [999, -1]; Helper.ListView.waitForReady(listView, -1)(). then(function () { listView.ensureVisible(ensureVisibleIndices[0]); return Helper.ListView.waitForReady(listView, -1)(); }). then(function () { range = { indexOfFirstVisible: listView.indexOfFirstVisible, indexOfLastVisible: listView.indexOfLastVisible }; LiveUnit.Assert.isTrue(ensureVisibleIndices[0] >= range.indexOfFirstVisible && ensureVisibleIndices[0] <= range.indexOfLastVisible); listView.ensureVisible(ensureVisibleIndices[1]); return Helper.ListView.waitForReady(listView, -1)(); }). then(function () { LiveUnit.Assert.areEqual(range.indexOfFirstVisible, listView.indexOfFirstVisible); clearTimeout(promiseErrorsTimeout); }). then(Helper.validateUnhandledErrorsOnIdle). done(complete); }; }; generateEnsureVisibleNegative("GridLayout"); function generateEnsureVisibleEmpty(layoutName) { BrowseModeTests.prototype["testEnsureVisibleEmpty" + layoutName] = function (complete) { var data = []; var bindingList = new WinJS.Binding.List(data); var listView = new WinJS.UI.ListView(list, { itemDataSource: bindingList.dataSource, layout: new WinJS.UI[layoutName](), }); Helper.initUnhandledErrors(); var range; var promiseErrorsTimeout = setTimeout(function () { Helper.validateUnhandledErrors(); }, 9900); var ensureVisibleIndices = [999, -1]; Helper.ListView.waitForReady(listView, -1)(). then(function () { listView.ensureVisible(ensureVisibleIndices[0]); return WinJS.Promise.timeout(500); }). then(function () { LiveUnit.Assert.areEqual(-1, listView.indexOfFirstVisible); listView.ensureVisible(ensureVisibleIndices[1]); return WinJS.Promise.timeout(500); }). then(function () { LiveUnit.Assert.areEqual(-1, listView.indexOfFirstVisible); clearTimeout(promiseErrorsTimeout); }). then(Helper.validateUnhandledErrorsOnIdle). done(complete); } }; generateEnsureVisibleEmpty("GridLayout"); function generateIndexOfFirstVisibleNegative(layoutName) { BrowseModeTests.prototype["testIndexOfFirstVisibleNegative" + layoutName] = function (complete) { var data = []; for (var i = 0; i < 1000; i++) { data.push("Item" + i); } var bindingList = new WinJS.Binding.List(data); var listView = new WinJS.UI.ListView(list, { itemDataSource: bindingList.dataSource, layout: new WinJS.UI[layoutName](), }); Helper.initUnhandledErrors(); var range; var promiseErrorsTimeout = setTimeout(function () { Helper.validateUnhandledErrors(); }, 9900); var indexOfFirstVisibleIndices = [999, -1]; Helper.ListView.waitForReady(listView, -1)(). then(function () { listView.indexOfFirstVisible = indexOfFirstVisibleIndices[0]; return Helper.ListView.waitForReady(listView, -1)(); }). then(function () { range = { indexOfFirstVisible: listView.indexOfFirstVisible, indexOfLastVisible: listView.indexOfLastVisible }; LiveUnit.Assert.isTrue(indexOfFirstVisibleIndices[0] >= range.indexOfFirstVisible && indexOfFirstVisibleIndices[0] <= range.indexOfLastVisible); listView.indexOfFirstVisible = indexOfFirstVisibleIndices[1]; return Helper.ListView.waitForReady(listView, -1)(); }). then(function () { LiveUnit.Assert.areEqual(range.indexOfFirstVisible, listView.indexOfFirstVisible); clearTimeout(promiseErrorsTimeout); }). then(Helper.validateUnhandledErrorsOnIdle). done(complete); }; }; generateIndexOfFirstVisibleNegative("GridLayout"); function generateEnsureVisibleScroll(layoutName) { BrowseModeTests.prototype["testEnsureVisibleScroll" + layoutName] = function (complete) { var data = []; for (var i = 0; i < 1000; i++) { data.push("Item" + i); } var bindingList = new WinJS.Binding.List(data); var listView = new WinJS.UI.ListView(list, { layout: new WinJS.UI[layoutName](), itemDataSource: bindingList.dataSource }); var visibleAt900; Helper.ListView.waitForReady(listView)(). then(function () { listView.ensureVisible(900); return Helper.ListView.waitForReady(listView)(); }). then(function () { visibleAt900 = listView.indexOfFirstVisible; LiveUnit.Assert.isTrue(listView.indexOfFirstVisible > 10); listView.ensureVisible(0); return Helper.ListView.waitForReady(listView)(); }). then(function () { LiveUnit.Assert.areEqual(0, listView.indexOfFirstVisible); listView.ensureVisible(900); return Helper.ListView.waitForReady(listView)(); }). then(function () { LiveUnit.Assert.areEqual(visibleAt900, listView.indexOfFirstVisible); listView.ensureVisible(-1); return Helper.ListView.waitForReady(listView)(); }). then(function () { // invalid request should be ignored LiveUnit.Assert.areEqual(visibleAt900, listView.indexOfFirstVisible); return Helper.ListView.waitForReady(listView)(); }). done(complete, function (e) { throw "Unexpected exception: " + e; }); }; }; generateEnsureVisibleScroll("GridLayout"); function generateHeaderKeyboardNavigationTest(layout) { function groupKey(item) { return item.groupKey; } function groupData(item) { return { key: groupKey(item), title: groupKey(item) }; } function createKeyEvent(element, key) { return { keyCode: key, target: element, stopPropagation: function () { }, preventDefault: function () { } }; } var a = "A".charCodeAt(0), items = []; for (var i = 0; i < 100; ++i) { items[i] = { title: "Item " + i, groupKey: String.fromCharCode(a + Math.floor(i / 5)) }; } var data = new WinJS.Binding.List(items).createGrouped(groupKey, groupData); BrowseModeTests.prototype["testHeaderKeyboardNavigation" + layout] = function (complete) { var lv = new ListView(list); lv.layout = new WinJS.UI[layout]; lv.itemDataSource = data.dataSource; lv.groupDataSource = data.groups.dataSource; function setFocus(entity) { function getEntity(entity) { if (entity.type === "item") { return lv.elementFromIndex(entity.index); } else { return lv.element.querySelectorAll(".win-groupheadercontainer")[entity.index]; } } function waitForFocus(element) { return new WinJS.Promise(function (c) { var token = setInterval(function () { if (document.activeElement === element || element.contains(document.activeElement)) { clearInterval(token); c(); } }, 100); }); } return WinJS.Promise.wrap().then(function () { // Ensure that entity is visible so that we'll be able to give it focus lv.ensureVisible(entity); return Helper.ListView.waitForReady(lv)(); }).then(function () { // Give entity focus and when it receives focus, the returned promise completes lv.currentItem = { type: entity.type, index: entity.index, hasFocus: true, showFocus: true }; // Sometimes setActive will throw so the item won't receive focus. To allow the // test to continue when this happens, use a timeout as a fallback. return WinJS.Promise.any([waitForFocus(getEntity(entity)), WinJS.Promise.timeout(500)]); }); } // Let's wait until the tree is fully created Helper.ListView.waitForAllContainers(lv).then(function () { // Navigating backwards return setFocus({ type: WinJS.UI.ObjectType.groupHeader, index: 5 }); }).then(function () { lv._mode.onKeyDown(createKeyEvent(document.querySelector(".win-groupheader"), (layout === "ListLayout" ? Key.upArrow : Key.leftArrow))); return Helper.ListView.waitForReady(lv, -1)(); }).then(function () { LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.groupHeader, lv.currentItem.type); LiveUnit.Assert.areEqual(4, lv.currentItem.index); // Navigating forwards return setFocus({ type: WinJS.UI.ObjectType.groupHeader, index: 5 }); }).then(function () { lv._mode.onKeyDown(createKeyEvent(document.querySelector(".win-groupheader"), (layout === "ListLayout" ? Key.downArrow : Key.rightArrow))); return Helper.ListView.waitForReady(lv, -1)(); }).then(function () { LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.groupHeader, lv.currentItem.type); LiveUnit.Assert.areEqual(6, lv.currentItem.index); // Lower bound check return setFocus({ type: WinJS.UI.ObjectType.groupHeader, index: 0 }); }).then(function () { lv._mode.onKeyDown(createKeyEvent(document.querySelector(".win-groupheader"), (layout === "ListLayout" ? Key.upArrow : Key.leftArrow))); return Helper.ListView.waitForReady(lv, -1)(); }).then(function () { LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.groupHeader, lv.currentItem.type); LiveUnit.Assert.areEqual(0, lv.currentItem.index); // Upper bound check return setFocus({ type: WinJS.UI.ObjectType.groupHeader, index: 19 }); }).then(function () { lv._mode.onKeyDown(createKeyEvent(document.querySelector(".win-groupheader"), (layout === "ListLayout" ? Key.downArrow : Key.rightArrow))); return Helper.ListView.waitForReady(lv, -1)(); }).then(function () { LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.groupHeader, lv.currentItem.type); LiveUnit.Assert.areEqual(19, lv.currentItem.index); // Home return setFocus({ type: WinJS.UI.ObjectType.groupHeader, index: 5 }); }).then(function () { lv._mode.onKeyDown(createKeyEvent(document.querySelector(".win-groupheader"), Key.home)); return Helper.ListView.waitForReady(lv, -1)(); }).then(function () { LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.groupHeader, lv.currentItem.type); LiveUnit.Assert.areEqual(0, lv.currentItem.index); // End return setFocus({ type: WinJS.UI.ObjectType.groupHeader, index: 5 }); }).then(function () { lv._mode.onKeyDown(createKeyEvent(document.querySelector(".win-groupheader"), Key.end)); return Helper.ListView.waitForReady(lv, -1)(); }).then(function () { LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.groupHeader, lv.currentItem.type); LiveUnit.Assert.areEqual(19, lv.currentItem.index); // Page up return setFocus({ type: WinJS.UI.ObjectType.groupHeader, index: 5 }); }).then(function () { lv._mode.onKeyDown(createKeyEvent(document.querySelector(".win-groupheader"), Key.pageUp)); return Helper.ListView.waitForReady(lv, -1)(); }).then(function () { LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.item, lv.currentItem.type); // Page down return setFocus({ type: WinJS.UI.ObjectType.groupHeader, index: 5 }); }).then(function () { return Helper.ListView.waitForReady(lv, -1)(); }).then(function () { lv._mode.onKeyDown(createKeyEvent(document.querySelector(".win-groupheader"), Key.pageDown)); return Helper.ListView.waitForReady(lv, -1)(); }).then(function () { LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.item, lv.currentItem.type); lv.header = document.createElement("div"); lv.footer = document.createElement("div"); return setFocus({ type: WinJS.UI.ObjectType.groupHeader, index: 0 }); }).then(function () { LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.groupHeader, lv.currentItem.type); lv._mode.onKeyDown(createKeyEvent(document.querySelector(".win-groupheader"), Key.leftArrow)); return Helper.ListView.waitForReady(lv, -1)(); }).then(function () { LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.header, lv.currentItem.type); lv._mode.onKeyDown(createKeyEvent(document.querySelector(".win-groupheader"), Key.end)); return Helper.ListView.waitForReady(lv, -1)(); }).then(function () { LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.footer, lv.currentItem.type); lv.header = null; lv._mode.onKeyDown(createKeyEvent(document.querySelector(".win-groupheader"), Key.home)); return Helper.ListView.waitForReady(lv, -1)(); }).then(function () { LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.groupHeader, lv.currentItem.type); lv.header = document.createElement("div"); lv._mode.onKeyDown(createKeyEvent(document.querySelector(".win-groupheader"), Key.leftArrow)); return Helper.ListView.waitForReady(lv, -1)(); }).then(function () { LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.header, lv.currentItem.type); lv.footer = null; lv._mode.onKeyDown(createKeyEvent(document.querySelector(".win-groupheader"), Key.end)); return Helper.ListView.waitForReady(lv, -1)(); }).done(function () { LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.groupHeader, lv.currentItem.type); complete(); }); }; }; generateHeaderKeyboardNavigationTest("ListLayout"); generateHeaderKeyboardNavigationTest("GridLayout"); function generateLastFocusedItemIndexChangeForGroupHeaderTest(layout) { function groupKey(item) { return item.groupKey; } function groupData(item) { return { key: groupKey(item), title: groupKey(item) }; } function createKeyEvent(element, key) { return { keyCode: key, target: element, stopPropagation: function () { }, preventDefault: function () { } }; } var a = "A".charCodeAt(0), items = []; for (var i = 0; i < 100; ++i) { items[i] = { title: "Item " + i, groupKey: String.fromCharCode(a + Math.floor(i / 5)) }; } var data = new WinJS.Binding.List(items).createGrouped(groupKey, groupData); BrowseModeTests.prototype["testLastFocusedItemIndexChangeForGroupHeader" + layout] = function (complete) { var lv = new ListView(list); lv.layout = new WinJS.UI[layout]; lv.itemDataSource = data.dataSource; lv.groupDataSource = data.groups.dataSource; Helper.ListView.waitForReady(lv)().then(function () { // Focus item, go to group header, remove last focused item from the group and go to item track again, focus should go on first item of the group lv.currentItem = { type: WinJS.UI.ObjectType.groupHeader, index: 4 }; lv.currentItem = { type: WinJS.UI.ObjectType.item, index: 22 }; lv._mode.onTabExiting({ detail: 1, preventDefault: function () { } }); LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.groupHeader, lv.currentItem.type); lv.itemDataSource.remove("22"); lv._mode.onTabExiting({ detail: 0, preventDefault: function () { } }); LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.item, lv.currentItem.type); LiveUnit.Assert.areEqual(20, lv.currentItem.index); // Focus item, go to group header, insert item at the place of last focused item from the group and go to item track again // Focus should go on last visited item not on new inserted item or not on first item of the group lv.currentItem = { type: WinJS.UI.ObjectType.groupHeader, index: 18 }; lv.currentItem = { type: WinJS.UI.ObjectType.item, index: 93 }; var itemKey = lv.currentItem.key; lv._mode.onTabExiting({ detail: 1, preventDefault: function () { } }); LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.groupHeader, lv.currentItem.type); LiveUnit.Assert.areEqual(18, lv.currentItem.index); lv.itemDataSource.insertAfter("newItem92", { title: "S", groupKey: "S" }, "91"); lv.itemDataSource.insertAfter("newItem94", { title: "S", groupKey: "S" }, "93"); lv.itemDataSource.insertAfter("newItem93", { title: "S", groupKey: "S" }, "92"); lv.itemDataSource.insertAfter("newItem95", { title: "S", groupKey: "S" }, "94"); lv._mode.onTabExiting({ detail: 0, preventDefault: function () { } }); LiveUnit.Assert.areEqual(WinJS.UI.ObjectType.item, lv.currentItem.type); LiveUnit.Assert.areEqual(itemKey, lv.currentItem.key); complete(); }); }; } generateLastFocusedItemIndexChangeForGroupHeaderTest("ListLayout"); generateLastFocusedItemIndexChangeForGroupHeaderTest("GridLayout"); function generateOffscreenPageKeysTest(layout) { function groupKey(item) { return item.groupKey; } function groupData(item) { return { key: groupKey(item), title: groupKey(item) }; } function createKeyEvent(element, key) { return { keyCode: key, target: element, stopPropagation: function () { }, preventDefault: function () { } }; } var a = "A".charCodeAt(0), items = []; for (var i = 0; i < 100; ++i) { items[i] = { title: "Item " + i, groupKey: String.fromCharCode(a + Math.floor(i / 5)) }; } var data = new WinJS.Binding.List(items).createGrouped(groupKey, groupData); BrowseModeTests.prototype["testOffscreenPageKeys" + layout] = function (complete) { var lv = new ListView(list); lv.layout = new WinJS.UI[layout]; lv.itemDataSource = data.dataSource; lv.groupDataSource = data.groups.dataSource; lv.currentItem = { type: WinJS.UI.ObjectType.item, index: 1 }; var origScrollPos; var indexOfFirstVisible; Helper.ListView.waitForReady(lv)().then(function () { origScrollPos = lv.scrollPosition; lv.scrollPosition = 1000; return Helper.ListView.waitForReady(lv, -1)(); }).then(function () { indexOfFirstVisible = lv.indexOfFirstVisible; lv._mode.onKeyDown(createKeyEvent(lv.element, Key.pageDown)); return Helper.ListView.waitForReady(lv, -1)(); }).then(function () { LiveUnit.Assert.areNotEqual(indexOfFirstVisible, lv.indexOfFirstVisible); complete(); }); }; }; generateOffscreenPageKeysTest("ListLayout"); generateOffscreenPageKeysTest("GridLayout"); } // register the object as a test class by passing in the name LiveUnit.registerTestClass("WinJSTests.BrowseModeTests");