// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License.txt in the project root for license information. // // /// /// /// /// // var VirtualizeContentsViewTestHost; module WinJSTests { "use strict"; var ListView = WinJS.UI.ListView; var ListLayout = WinJS.UI.ListLayout; var SemanticZoom = WinJS.UI.SemanticZoom; var defaultDisableCustomPagesPrefetch; var defaultIsiOS; var BIG_DATASET = 15000, COUNT = 100, STRUCTURENODE_SIZE = 8; function initData(count?, groupSize?) { count = count || COUNT; groupSize = groupSize || 10; var items = []; for (var i = 0; i < count; ++i) { items[i] = { title: "Tile" + i, group: Math.floor(i / groupSize) }; } return items; } function groupKey(data) { return data.group.toString(); } function groupData(data) { return { title: data.group.toString() }; } function createListViewElement(height?) { var element = document.createElement("div"); element.style.width = "300px"; element.style.height = height ? height : "300px"; VirtualizeContentsViewTestHost.appendChild(element); return element; } function generateRenderer(size, prefix?) { return function (itemPromise) { return itemPromise.then(function (item) { var element = document.createElement("div"); element.className = "myVVTestClass"; element.textContent = prefix ? prefix + item.data.title : item.data.title; element.style.width = element.style.height = size; element.style.backgroundColor = "blue"; return element; }); } } function createPointerEvent(element) { var rect = element.getBoundingClientRect(); return { target: element, button: WinJS.UI._LEFT_MSPOINTER_BUTTON, clientX: (rect.left + rect.right) / 2, clientY: (rect.top + rect.bottom) / 2, preventDefault: function () { } }; } var testRootEl; var defaultChunkSize, defaultMaxTime, defaultPagesToPrefetch; function validateFlatTree(listView, expectedItemsPerBlock?) { expectedItemsPerBlock = expectedItemsPerBlock || STRUCTURENODE_SIZE; var containers; LiveUnit.Assert.areEqual(1, listView.element.querySelectorAll(".win-itemscontainer").length); LiveUnit.Assert.areEqual(0, listView.element.querySelectorAll(".win-groupheadercontainer").length); if (!listView._view.tree[0].itemsContainer.itemsBlocks) { containers = listView.element.querySelectorAll(".win-container"); LiveUnit.Assert.areEqual(listView.itemDataSource.list.length, containers.length); for (var i = 0, len = containers.length; i < len; i++) { var container = containers[i], itemBox = container.firstElementChild; LiveUnit.Assert.areEqual(container, listView._view.containers[i]); if (itemBox) { LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(itemBox, "win-itembox")); LiveUnit.Assert.areEqual(listView.selection._isIncluded(i), WinJS.Utilities.hasClass(container, "win-selected")); LiveUnit.Assert.areEqual(listView.selection._isIncluded(i), WinJS.Utilities.hasClass(itemBox, "win-selected")); } else { LiveUnit.Assert.isFalse(WinJS.Utilities.hasClass(container, "win-selected")); } LiveUnit.Assert.areEqual(container, listView._view.tree[0].itemsContainer.items[i]); } LiveUnit.Assert.areEqual(containers.length - 1, listView._view.lastItemIndex()); } else { var blocks = listView._view.tree[0].itemsContainer.itemsBlocks; for (var i = 0, itemIndex = 0, len = blocks.length; i < len; i++) { var block = blocks[i]; LiveUnit.Assert.isTrue(i + 1 >= len || block.element.children.length === expectedItemsPerBlock); for (var n = 0; n < block.items.length; n++) { LiveUnit.Assert.areEqual(listView._view.containers[itemIndex++], block.items[n]); } } verifyContainerStripesByIndex(listView); } } function verifyTreeUpdate(layout, validate, complete) { WinJS.UI._VirtualizeContentsView._chunkSize = 15; WinJS.UI._VirtualizeContentsView._maxTimePerCreateContainers = Number.MAX_VALUE; var placeholder = createListViewElement(); var list = new WinJS.Binding.List(initData()), listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: generateRenderer("50px"), layout: layout }); listView.selection.set([0, 5, 10]); return Helper.ListView.waitForReady(listView)().then(function () { validate(listView); list.splice(9, 1); list.shift(); list.shift(); list.shift(); list.unshift({ title: "NewItem0", group: 0 }); return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { validate(listView); for (var i = 0; i < 10; i++) { list.unshift({ title: "NI" + i, group: 0 }); } return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { validate(listView); while (list.length) { list.pop(); } return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { validate(listView); list.unshift({ title: "SNI", group: 0 }); return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { validate(listView); placeholder.parentNode.removeChild(placeholder); complete(); }); } function validateGroupedTree(listView, expectedItemsPerBlock?) { expectedItemsPerBlock = expectedItemsPerBlock || STRUCTURENODE_SIZE; var containers, itemsContainers = listView.element.querySelectorAll(".win-itemscontainer"), headers = listView.element.querySelectorAll(".win-groupheadercontainer"); LiveUnit.Assert.areEqual(listView.groupDataSource.list.length, itemsContainers.length); LiveUnit.Assert.areEqual(listView.groupDataSource.list.length, headers.length); if (!listView._view.tree[0].itemsContainer.itemsBlocks) { containers = listView.element.querySelectorAll(".win-container"); LiveUnit.Assert.areEqual(listView.itemDataSource.list.length, containers.length); for (var i = 0, len = containers.length; i < len; i++) { var container = containers[i], itemBox = container.firstElementChild; LiveUnit.Assert.areEqual(container, listView._view.containers[i]); if (itemBox) { LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(itemBox, "win-itembox")); LiveUnit.Assert.areEqual(listView.selection._isIncluded(i), WinJS.Utilities.hasClass(container, "win-selected")); LiveUnit.Assert.areEqual(listView.selection._isIncluded(i), WinJS.Utilities.hasClass(itemBox, "win-selected")); } else { LiveUnit.Assert.isFalse(WinJS.Utilities.hasClass(container, "win-selected")); } } LiveUnit.Assert.areEqual(containers.length - 1, listView._view.lastItemIndex()); } else { verifyContainerStripesByIndex(listView); } var prevElement = listView.element.querySelector("._win-proxy"); for (var i = 0, itemIndex = 0, blockIndex = 0, len = listView._view.tree.length; i < len; i++) { var group = listView._view.tree[i]; LiveUnit.Assert.areEqual(headers[i], group.header); LiveUnit.Assert.areEqual(itemsContainers[i], group.itemsContainer.element); LiveUnit.Assert.areEqual(prevElement.nextElementSibling, group.header); LiveUnit.Assert.areEqual(group.header.nextElementSibling, group.itemsContainer.element); if (group.itemsContainer.itemsBlocks) { var blocks = group.itemsContainer.itemsBlocks; for (var j = 0; j < blocks.length; j++) { var block = blocks[j]; LiveUnit.Assert.isTrue(j + 1 >= blocks.length || block.element.children.length === expectedItemsPerBlock); for (var n = 0; n < block.items.length; n++) { LiveUnit.Assert.areEqual(listView._view.containers[itemIndex++], block.items[n]); } } } else { for (var n = 0; n < group.itemsContainer.items.length; n++) { LiveUnit.Assert.areEqual(containers[itemIndex++], group.itemsContainer.items[n]); } } prevElement = group.itemsContainer.element; } } function verifyGroupedTreeUpdate(layout, validate, complete) { WinJS.UI._VirtualizeContentsView._chunkSize = 15; WinJS.UI._VirtualizeContentsView._maxTimePerCreateContainers = Number.MAX_VALUE; var placeholder = createListViewElement(); var list = new WinJS.Binding.List(initData()), groupedList = list.createGrouped(groupKey, groupData), listView = new ListView(placeholder, { itemDataSource: groupedList.dataSource, groupDataSource: groupedList.groups.dataSource, itemTemplate: generateRenderer("50px"), groupHeaderTemplate: generateRenderer("50px"), layout: layout }); listView.selection.set([0, 10, 20]); return Helper.ListView.waitForReady(listView)().then(function () { validate(listView); list.splice(25, 1); list.splice(10, 10); list.unshift({ title: "N1", group: 0 }); list.unshift({ title: "N0", group: 0 }); return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { validate(listView); for (var i = 0; i < 5; i++) { list.splice(12, 0, { title: "A" + i, group: 1 }); } return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { validate(listView); for (var i = 0; i < 5; i++) { list.unshift({ title: "B" + i, group: -1 }); } return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { listView.ensureVisible(list.length - 1); return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { validate(listView); for (var i = 0; i < 5; i++) { list.push({ title: "C" + i, group: 999 }); } return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { validate(listView); placeholder.parentNode.removeChild(placeholder); complete(); }); } function validatePagesToPrefetch(listView, expectedLeadingPages, expectedTrailingPages, viewPortHeight) { var elementsPerPage, expectedRealizedCount; // Verify property getters return the correct value. LiveUnit.Assert.areEqual(expectedLeadingPages, listView.maxLeadingPages); LiveUnit.Assert.areEqual(expectedTrailingPages, listView.maxTrailingPages); return Helper.ListView.waitForReady(listView, -1)().then(function () { elementsPerPage = (listView.indexOfLastVisible - listView.indexOfFirstVisible) + 1 // When we are at the top of the list (scroll:0), we should have the current viewport full of items + expectedLeadingPages pages ahead expectedRealizedCount = elementsPerPage + (expectedLeadingPages * elementsPerPage); LiveUnit.Assert.areEqual(expectedRealizedCount, listView.element.querySelectorAll(".win-container:not(.win-backdrop)").length); // Scroll to the bottom of the current viewport listView.scrollPosition = viewPortHeight; return Helper.ListView.waitForDeferredAction(listView)(); }).then(function () { // When our scroll position = viewPortHeight, we should have the current viewport full of items + up to 1 page behind + expectedLeadingPages pages ahead expectedRealizedCount = (expectedTrailingPages > 0 ? elementsPerPage : 0) /* up to 1 page behind*/ + elementsPerPage /* viewport*/ + (expectedLeadingPages * elementsPerPage); LiveUnit.Assert.areEqual(expectedRealizedCount, listView.element.querySelectorAll(".win-container:not(.win-backdrop)").length); // Scroll down expectedLeadingPages viewports listView.scrollPosition = expectedLeadingPages * viewPortHeight; return Helper.ListView.waitForDeferredAction(listView)(); }).then(function () { LiveUnit.Assert.areEqual(listView.indexOfFirstVisible, expectedLeadingPages * elementsPerPage); // Since we are scrolling downward, we optimize the front buffer, so we should have expectedTrailingPages pages behind + current viewport + expectedLeadingPages ahead expectedRealizedCount = (expectedTrailingPages * elementsPerPage) /* behind*/ + elementsPerPage /* viewport*/ + (expectedLeadingPages * elementsPerPage); LiveUnit.Assert.areEqual(expectedRealizedCount, listView.element.querySelectorAll(".win-container:not(.win-backdrop)").length); }); } function testLazyGroupedTreeCreation(data, complete) { WinJS.UI._VirtualizeContentsView._maxTimePerCreateContainers = 0; var placeholder = createListViewElement(); var list = new WinJS.Binding.List(data), groupedList = list.createGrouped(groupKey, groupData); var listView = new ListView(placeholder, { itemDataSource: groupedList.dataSource, groupDataSource: groupedList.groups.dataSource, itemTemplate: generateRenderer("100px"), groupHeaderTemplate: generateRenderer("50px"), layout: new WinJS.UI.ListLayout() }); return Helper.ListView.waitForReady(listView)().then(function () { return listView._view._creatingContainersWork ? listView._view._creatingContainersWork.promise : null; }).then(function () { validateGroupedTree(listView, ListLayout._numberOfItemsPerItemsBlock); placeholder.parentNode.removeChild(placeholder); complete(); }); } function waitForItemsLoaded(listView) { return new WinJS.Promise(function (complete) { function handler() { if (listView.loadingState === "itemsLoaded") { listView.removeEventListener("loadingstatechanged", handler, false); complete(); } } listView.addEventListener("loadingstatechanged", handler, false); }); } function generateAnimationInViewportPendingFlagTest(complete, firstIndexToInsert, expectedValue) { WinJS.UI._VirtualizeContentsView._disableCustomPagesPrefetch = true; var element = document.createElement("div"); element.style.width = "300px"; element.style.height = "300px"; VirtualizeContentsViewTestHost.appendChild(element); var list = createBindingList(100); var listView = new ListView(element, { layout: new WinJS.UI.ListLayout(), itemDataSource: list.dataSource, itemTemplate: function (itemPromise) { return itemPromise.then(function (item) { var el = document.createElement("div"); el.textContent = item.data.title; el.style.height = "20px"; el.style.width = "417px"; el.className = item.data.className ? item.data.className : "" return el; }); } }); Helper.ListView.waitForReady(listView)().then(function () { LiveUnit.Assert.areEqual(45, getNumberOfItemsRealized()); var oldStartAnimations = listView._view._startAnimations; listView._view._startAnimations = function () { LiveUnit.Assert.areEqual(expectedValue, listView._view._hasAnimationInViewportPending); var promise = oldStartAnimations.call(listView._view); LiveUnit.Assert.isFalse(listView._view._hasAnimationInViewportPending); return promise; }; for (var i = 0; i < 30; i++) { list.splice(firstIndexToInsert + i, 0, { title: "title New " + i, className: "new" }); } Helper.ListView.waitForDeferredAction(listView)().then(function () { LiveUnit.Assert.areEqual(45, getNumberOfItemsRealized()); VirtualizeContentsViewTestHost.removeChild(element); complete(); }); }); } function createData(size) { var data = []; for (var i = 0; i < size; i++) { data.push({ title: "title" + i, index: i, itemWidth: "80px", itemHeight: "80px" }); } return data; } function createBindingList(size, data?) { return (data ? new WinJS.Binding.List(data) : new WinJS.Binding.List(createData(size))); } function getNumberOfItemsRealized() { return document.querySelectorAll(".win-listview .win-itemscontainer .win-item").length; } function fixedSizeTemplate(itemPromise) { var el = document.createElement("div"); el.style.height = "20px"; el.style.width = "417px"; itemPromise.then(function (item) { el.textContent = JSON.stringify(item.data); }); return el; } function setupMailStyleListView() { var element = document.getElementById("testListView"); var items = []; for (var i = 0; i < 1000; ++i) { items[i] = { title: "Tile" + i }; } var dataSource; var controller = { directivesForMethod: function (method, args) { return { callMethodSynchronously: false, sendChangeNotifications: true, countBeforeDelta: 0, countAfterDelta: 0, countBeforeOverride: -1, countAfterOverride: -1, delay: 10 }; } }; dataSource = Helper.ItemsManager.createTestDataSource(items.slice(0), controller, null); return new ListView(element, { itemDataSource: dataSource, selectionMode: "multi", itemTemplate: Helper.ListView.createRenderer("simpleTemplate"), layout: new WinJS.UI.ListLayout() }); } var numberOfItemsPerItemsBlock = 10, itemHeight = 50; function verifyBlockInDom(listView, blocksExpectedInTree) { LiveUnit.Assert.areEqual(blocksExpectedInTree.length, listView.element.querySelectorAll(".win-itemsblock").length); var blockIndex = 0, offset = 0; var tree = listView._view.tree; for (var g = 0; g < tree.length; g++) { var groupBundle = tree[g], itemsContainer = groupBundle.itemsContainer; var padder = itemsContainer.element.firstElementChild; LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(padder, "win-itemscontainer-padder")); var children = [padder]; for (var b = 0; b < itemsContainer.itemsBlocks.length; b++) { var block = itemsContainer.itemsBlocks[b]; if (blocksExpectedInTree.indexOf(blockIndex) != -1) { LiveUnit.Assert.isTrue(block.element.parentNode === itemsContainer.element); LiveUnit.Assert.isTrue(block.element.children.length < numberOfItemsPerItemsBlock || (itemHeight * numberOfItemsPerItemsBlock) === block.element.offsetHeight); LiveUnit.Assert.areEqual(offset, Helper.ListView.offsetTopFromSurface(listView, block.element)); children.push(block.element); offset += block.element.offsetHeight; } else { LiveUnit.Assert.isFalse(block.element.parentNode === itemsContainer.element); offset += itemHeight * block.items.length; } blockIndex++; } for (var i = 0; i < children.length; i++) { LiveUnit.Assert.areEqual(children[i], itemsContainer.element.children[i]); } } } function initGroups(groups) { var items = [], itemIndex = 0; for (var g = 0; g < groups.length; g++) { for (var i = 0; i < groups[g]; ++i) { items.push({ title: "Tile" + itemIndex++, group: g }); } } return items; } function verifyContainerStripesByIndex(listView) { // Check correctness in each group var groups = [].slice.call(listView.element.querySelectorAll(".win-itemscontainer")); groups.forEach(function (group) { var containers = [].slice.call(group.querySelectorAll(".win-container")); // Verify containers have the right class containers.forEach(function (container, index) { if (index % 2 === 0) { LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(container, "win-container-even"), "Container with index: " + index + " doesn't have even class!!") } else { LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(container, "win-container-odd"), "Container with index: " + index + " doesn't have odd class!!") } }); }); }; function getPairWiseConfigurationsForSmallDataStripeTests() { var FlowLayout = WinJS.Class.define(function FlowLayout_ctor(blockSize) { this.initialize = function () { }, this.numberOfItemsPerItemsBlock = blockSize; }); // Couple the itemDataSource and groupDataSource together to avoid scenarios that might pair smallDataSource together with bigGroupedDataSource function generateSmallDataSources() { var smallList = new WinJS.Binding.List(initData()); return { itemDataSource: smallList.dataSource, groupDataSource: null } } function generateSmallGroupedDataSources() { var smallGroupedList = new WinJS.Binding.List(initData()).createGrouped(groupKey, groupData); return { itemDataSource: smallGroupedList.dataSource, groupDataSource: smallGroupedList.groups.dataSource }; } // Describe all relevant test inputs to pass to the ListView constructor var relevantConfigurations = { layoutConfiguration: [ // Each test will need its own Layout Object, so expose them through getters. { descriptor: "ListLayout", getter: function () { return new WinJS.UI.ListLayout() } }, { descriptor: "GridLayout", getter: function () { return new WinJS.UI.GridLayout() } }, { descriptor: "FlowLayout9", getter: function () { return new FlowLayout(9) } }, // 9 items per block to test FlowLayout with odd block size { descriptor: "FlowLayout10", getter: function () { return new FlowLayout(10) } } // 10 items per block to test FlowLayout with even block size ], dataConfiguration: [ // Each test needs its own set of data to modify, so expose them through getters { descriptor: "NonGroupedSmallDataSet", getter: function () { return generateSmallDataSources(); } }, { descriptor: "GroupedSmallDataSet", getter: function () { return generateSmallGroupedDataSources(); } }, ], }; // Use the pairwise helper to assemble all our configurations for testing container stripes var pairWiseConfigurations = Helper.pairwise(relevantConfigurations); return pairWiseConfigurations; } function getPairWiseConfigurationsForBigDataStripeTests() { // We use this instead of the larger BIG_DATASET constant, otherwise FireFox takes too long and the test times out. var bigDataSize = 3000; // Couple the itemDataSource and groupDataSource together to avoid scenarios that might pair smallDataSource together with bigGroupedDataSource function generateBigDataSources() { var bigList = new WinJS.Binding.List(initData(bigDataSize)); return { itemDataSource: bigList.dataSource, groupDataSource: null } } function generateBigGroupedDataSources() { var bigGroupedList = new WinJS.Binding.List(initData(bigDataSize)).createGrouped(groupKey, groupData); return { itemDataSource: bigGroupedList.dataSource, groupDataSource: bigGroupedList.groups.dataSource }; } // Describe all relevant test inputs to pass to the ListView constructor var relevantConfigurations = { layoutConfiguration: [ // Each test will need its own Layout Object, so expose them through getters. { descriptor: "ListLayout", getter: function () { return new WinJS.UI.ListLayout() } }, { descriptor: "GridLayout", getter: function () { return new WinJS.UI.GridLayout() } }, ], dataConfiguration: [ // Each test needs its own set of data to modify, so expose them through getters { descriptor: "NonGroupedBigDataSet", getter: function () { return generateBigDataSources(); } }, { descriptor: "GroupedBigDataSet", getter: function () { return generateBigGroupedDataSources(); } }, ], }; // Use the pairwise helper to assemble all our configurations for testing container stripes var pairWiseConfigurations = Helper.pairwise(relevantConfigurations); return pairWiseConfigurations; } export class VirtualizedViewTests { setUp(completed) { testRootEl = document.createElement("div"); testRootEl.className = "file-listview-css"; var testHost = document.createElement("div"); testHost.id = "VirtualizeContentsViewTestHost"; VirtualizeContentsViewTestHost = testHost; testRootEl.appendChild(testHost); document.body.appendChild(testRootEl); defaultChunkSize = WinJS.UI._VirtualizeContentsView._chunkSize; defaultMaxTime = WinJS.UI._VirtualizeContentsView._maxTimePerCreateContainers; defaultPagesToPrefetch = WinJS.UI._VirtualizeContentsView._defaultPagesToPrefetch; //WinBlue: 298587 WinJS.UI._VirtualizeContentsView._maxTimePerCreateContainers = Number.MAX_VALUE; defaultDisableCustomPagesPrefetch = WinJS.UI._VirtualizeContentsView._disableCustomPagesPrefetch; defaultIsiOS = WinJS.Utilities._isiOS; completed(); } tearDown = function () { WinJS.Utilities.disposeSubTree(testRootEl); document.body.removeChild(testRootEl); WinJS.UI._VirtualizeContentsView._chunkSize = defaultChunkSize; WinJS.UI._VirtualizeContentsView._maxTimePerCreateContainers = defaultMaxTime; WinJS.UI._VirtualizeContentsView._defaultPagesToPrefetch = defaultPagesToPrefetch; WinJS.UI._VirtualizeContentsView._disableCustomPagesPrefetch = defaultDisableCustomPagesPrefetch; WinJS.Utilities._setIsiOS(defaultIsiOS); }; testInitalization = function (complete) { function createListView(orientation) { var placeholder = createListViewElement(); var list = new WinJS.Binding.List(initData()); var refCount = 0; var newLayout = { initialize: function (site, groups) { refCount++; LiveUnit.Assert.isTrue(site.viewport === Helper.ListView.viewport(placeholder)); LiveUnit.Assert.isTrue(site.surface === Helper.ListView.canvas(placeholder)); LiveUnit.Assert.isFalse(groups); }, orientation: orientation, uninitialize: function () { LiveUnit.Assert.isTrue(refCount > 0); refCount--; }, layout: function (tree) { LiveUnit.Assert.areEqual(COUNT, tree[0].itemsContainer.items.length); LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(tree[0].itemsContainer.element, "win-itemscontainer")); for (var i = 0, len = tree[0].itemsContainer.items.length; i < len; i++) { var container = tree[0].itemsContainer.items[i]; LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(container, "win-container")); } return WinJS.Promise.wrap(); }, itemsFromRange: function (left, right) { return { firstIndex: 0, lastIndex: 9 }; } }; var listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: generateRenderer("100px"), layout: newLayout }); return Helper.ListView.waitForReady(listView)().then(function () { LiveUnit.Assert.areEqual(10, placeholder.querySelectorAll(".win-item").length); var containers = placeholder.querySelectorAll(".win-container"); LiveUnit.Assert.areEqual(COUNT, containers.length); for (var i = 0; i < containers.length; i++) { var container = containers[i]; LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(container, "win-container")); if (i < 10) { LiveUnit.Assert.areEqual(1, container.childElementCount); LiveUnit.Assert.areEqual(("Tile" + i), container.textContent); } else { LiveUnit.Assert.areEqual(0, container.childElementCount); } } return { listView: listView, verifyRefCount: function () { LiveUnit.Assert.areEqual(0, refCount); } }; }); } createListView("vertical").then(function (result) { var element = result.listView.element, vp = Helper.ListView.viewport(element); LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(vp, "win-vertical")); LiveUnit.Assert.isTrue("hidden", window.getComputedStyle(vp, null).overflowX); LiveUnit.Assert.isTrue("scroll", window.getComputedStyle(vp, null).overflowY); element.parentNode.removeChild(element); return createListView("horizontal"); }).then(function (result) { var element = result.listView.element, vp = Helper.ListView.viewport(element); LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(vp, "win-horizontal")); LiveUnit.Assert.isTrue("scroll", window.getComputedStyle(vp, null).overflowX); LiveUnit.Assert.isTrue("hidden", window.getComputedStyle(vp, null).overflowY); element.parentNode.removeChild(element); result.listView._dispose(); result.verifyRefCount(); complete(); }); }; testGroupedInitialization = function (complete) { WinJS.UI._VirtualizeContentsView._maxTimePerCreateContainers = Number.MAX_VALUE; var placeholder = createListViewElement(); var list = new WinJS.Binding.List(initData()); var myGroupedList = list.createGrouped(groupKey, groupData); var newLayout = { initialize: function (site, groups) { LiveUnit.Assert.isTrue(groups); return "vertical"; }, layout: function (tree) { LiveUnit.Assert.isTrue(Array.isArray(tree)); LiveUnit.Assert.areEqual(10, tree.length); for (var g = 0; g < tree.length; g++) { var group = tree[g]; LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(group.header, "win-groupheadercontainer")); var itemsContainer = group.itemsContainer; LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(itemsContainer.element, "win-itemscontainer")); LiveUnit.Assert.areEqual(10, itemsContainer.items.length); for (var i = 0; i < itemsContainer.items.length; i++) { var container = itemsContainer.items[i]; LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(container, "win-container")); } } return WinJS.Promise.wrap(); } }; var listView = new ListView(placeholder, { itemDataSource: myGroupedList.dataSource, groupDataSource: myGroupedList.groups.dataSource, itemTemplate: generateRenderer("100px"), groupHeaderTemplate: generateRenderer("50px"), layout: newLayout }); return Helper.ListView.waitForReady(listView)().then(function () { LiveUnit.Assert.areEqual(COUNT / 10, placeholder.querySelectorAll(".win-groupheader").length); LiveUnit.Assert.areEqual(COUNT / 10, placeholder.querySelectorAll(".win-groupheadercontainer").length); LiveUnit.Assert.areEqual(COUNT, placeholder.querySelectorAll(".win-item").length); var containers = placeholder.querySelectorAll(".win-container"); LiveUnit.Assert.areEqual(COUNT, containers.length); for (var i = 0; i < containers.length; i++) { var container = containers[i]; LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(container, "win-container")); LiveUnit.Assert.areEqual(("Tile" + i), container.textContent); } placeholder.parentNode.removeChild(placeholder); complete(); }); }; testLayoutWrapper = function (complete) { var placeholder = createListViewElement(); var newLayout = { initialize: function (site, groups) { return "vertical"; } }; var listView = new ListView(placeholder, { itemDataSource: (new WinJS.Binding.List(initData())).dataSource, itemTemplate: generateRenderer("100px"), layout: newLayout }); return Helper.ListView.waitForReady(listView)().then(function () { LiveUnit.Assert.areEqual(COUNT, placeholder.querySelectorAll(".win-item").length); LiveUnit.Assert.areEqual(COUNT, placeholder.querySelectorAll(".win-container").length); var layout = listView._layout; LiveUnit.Assert.isTrue(typeof layout.uninitialize === "function"); LiveUnit.Assert.isTrue(typeof layout.layout === "function"); LiveUnit.Assert.isTrue(typeof layout.itemsFromRange === "function"); LiveUnit.Assert.isTrue(typeof layout.dragOver === "function"); LiveUnit.Assert.isTrue(typeof layout.dragLeave === "function"); LiveUnit.Assert.isTrue(typeof layout.setupAnimations === "function"); LiveUnit.Assert.isTrue(typeof layout.executeAnimations === "function"); function moveFocus(current, key) { return layout.getAdjacent({ type: "item", index: current }, key).index; } var key = WinJS.Utilities.Key; LiveUnit.Assert.areEqual(0, moveFocus(1, key.leftArrow)); LiveUnit.Assert.areEqual(0, moveFocus(1, key.upArrow)); LiveUnit.Assert.areEqual(0, moveFocus(1, key.pageUp)); LiveUnit.Assert.areEqual(1, moveFocus(0, key.rightArrow)); LiveUnit.Assert.areEqual(1, moveFocus(0, key.downArrow)); LiveUnit.Assert.areEqual(1, moveFocus(0, key.pageDown)); placeholder.parentNode.removeChild(placeholder); complete(); }); }; testUninitialize = function (complete) { function createListView(oldPlaceholder?) { if (oldPlaceholder) { oldPlaceholder.parentNode.removeChild(oldPlaceholder); } var placeholder = createListViewElement(); var state = { refCount: 0, canceled: false, layout: false }; var newLayout = { initialize: function (site, groups) { state.refCount++; return "vertical"; }, uninitialize: function () { LiveUnit.Assert.isTrue(state.refCount > 0 || state.canceled); state.refCount--; }, layout: function (tree, groups) { state.layout = true; return WinJS.Promise.timeout(10000).then(null, function (error) { if (error.name === "Canceled") { LiveUnit.Assert.isTrue(state.refCount > 0); state.canceled = true; } }); } }; var listView = new ListView(placeholder, { itemDataSource: (new WinJS.Binding.List(initData())).dataSource, itemTemplate: generateRenderer("100px"), layout: newLayout }); return new WinJS.Promise(function (c, e, p) { WinJS.Utilities.Scheduler.schedule(c, WinJS.Utilities.Scheduler.Priority.high); }).then(function () { LiveUnit.Assert.isTrue(state.layout); return { listView: listView, state: state }; }); } createListView().then(function (result) { var element = result.listView.element; element.parentNode.removeChild(element); result.listView._dispose(); LiveUnit.Assert.areEqual(0, result.state.refCount); LiveUnit.Assert.isTrue(result.state.canceled); return createListView(); }).then(function (result) { result.listView.itemDataSource = (new WinJS.Binding.List(initData())).dataSource; LiveUnit.Assert.isTrue(result.state.canceled); return createListView(result.listView.element); }).then(function (result) { result.listView.itemTemplate = generateRenderer("50px"); LiveUnit.Assert.isTrue(result.state.canceled); return createListView(result.listView.element); }).then(function (result) { result.listView.layout = { initialize: function (site, groups) { return "horizontal"; } }; LiveUnit.Assert.isTrue(result.state.canceled); return createListView(result.listView.element); }).then(function (result) { result.listView.forceLayout(); return WinJS.Promise.timeout().then(function () { return result; }); }).then(function (result) { LiveUnit.Assert.isTrue(result.state.canceled); return createListView(result.listView.element); }).then(function (result) { result.listView.recalculateItemPosition(); return WinJS.Promise.timeout().then(function () { return result; }); }).then(function (result) { LiveUnit.Assert.isTrue(result.state.canceled); return createListView(result.listView.element); }).then(function (result) { var list = result.listView.itemDataSource.list; list.shift(); return WinJS.Promise.timeout().then(function () { return result; }); }).then(function (result) { LiveUnit.Assert.isTrue(result.state.canceled); result.listView.element.parentNode.removeChild(result.listView.element); complete(); }); }; testGetItemPosition = function (complete) { var placeholder = createListViewElement(); var listView = new ListView(placeholder, { itemDataSource: (new WinJS.Binding.List(initData())).dataSource, itemTemplate: generateRenderer("100px"), layout: { initialize: function (site, groups) { return "vertical"; } } }); return Helper.ListView.waitForReady(listView)().then(function () { listView.ensureVisible(50); return Helper.ListView.waitForReady(listView)(); }).then(function () { LiveUnit.Assert.areEqual(4800, Helper.ListView.viewport(placeholder).scrollTop); placeholder.parentNode.removeChild(placeholder); complete(); }); }; testClickOnContainer = function (complete) { var placeholder = createListViewElement(); var listView = new ListView(placeholder, { itemDataSource: (new WinJS.Binding.List(initData())).dataSource, itemTemplate: generateRenderer("100px"), layout: { initialize: function (site, groups) { return "vertical"; }, itemsFromRange: function () { return { firstIndex: 0, lastIndex: 0 }; } } }); var invokedItem = -1; listView.addEventListener("iteminvoked", function (eventObject) { invokedItem = eventObject.detail.itemIndex; }); return Helper.ListView.waitForReady(listView)().then(function () { LiveUnit.Assert.areEqual(-1, invokedItem); var realizedItems = placeholder.querySelectorAll(".win-item"); LiveUnit.Assert.areEqual(1, realizedItems.length); LiveUnit.Assert.areEqual(realizedItems[0], listView.elementFromIndex(0)); var realizedItem = realizedItems[0]; listView._currentMode()._itemEventsHandler._getCurrentPoint = function (eventObject) { return { properties: { isRightButtonPressed: eventObject.button === WinJS.UI._RIGHT_MSPOINTER_BUTTON, isLeftButtonPressed: eventObject.button === WinJS.UI._LEFT_MSPOINTER_BUTTON } }; }; realizedItem.scrollIntoView(false); listView._currentMode().onPointerDown(createPointerEvent(realizedItem)); listView._currentMode().onPointerUp(createPointerEvent(realizedItem)); LiveUnit.Assert.areEqual(0, invokedItem); invokedItem = -1; LiveUnit.Assert.areEqual(null, listView.elementFromIndex(1)); var container = placeholder.querySelectorAll(".win-container")[1]; listView._currentMode().onPointerDown(createPointerEvent(container)); listView._currentMode().onPointerUp(createPointerEvent(container)); LiveUnit.Assert.areEqual(-1, invokedItem); placeholder.parentNode.removeChild(placeholder); complete(); }).done(); }; testLayoutSite = function (complete) { var placeholder = createListViewElement(); var layoutSite; function testSite(tree) { LiveUnit.Assert.areEqual(Helper.ListView.canvas(placeholder), layoutSite.surface); LiveUnit.Assert.areEqual(Helper.ListView.viewport(placeholder), layoutSite.viewport); LiveUnit.Assert.areEqual(0, layoutSite.scrollbarPos); LiveUnit.Assert.areEqual(300, layoutSite.viewportSize.width); LiveUnit.Assert.areEqual(300, layoutSite.viewportSize.height); LiveUnit.Assert.isFalse(false, layoutSite.viewportSize.rtl); LiveUnit.Assert.areEqual("randomAccess", layoutSite.loadingBehavior); LiveUnit.Assert.areEqual(tree, layoutSite.tree); var itemPromise = layoutSite.itemFromIndex(11); return itemPromise.then(function (item) { LiveUnit.Assert.areEqual("Tile11", item.data.title); LiveUnit.Assert.areEqual(11, item.index); LiveUnit.Assert.areEqual("1", item.groupKey); return layoutSite.renderItem(itemPromise); }).then(function (element) { LiveUnit.Assert.isTrue(element instanceof HTMLElement); LiveUnit.Assert.areEqual("Tile11", element.textContent); LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(element, "win-container")); LiveUnit.Assert.areEqual(1, element.childElementCount); var itemBox = element.firstElementChild; LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(itemBox, "win-itembox")); LiveUnit.Assert.areEqual(1, itemBox.childElementCount); var itemNode = itemBox.firstElementChild; LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(itemNode, "win-item")); LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(itemNode, "myVVTestClass")); var groupIndex = layoutSite.groupIndexFromItemIndex(11); LiveUnit.Assert.areEqual(1, groupIndex); var group = layoutSite.groupFromIndex(groupIndex); LiveUnit.Assert.areEqual("1", group.data.title); LiveUnit.Assert.areEqual("1", group.key); LiveUnit.Assert.areEqual(10, group.firstItemIndexHint); return layoutSite.renderHeader(group); }).then(function (header) { LiveUnit.Assert.isTrue(header instanceof HTMLElement); LiveUnit.Assert.areEqual("Header1", header.textContent); LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(header, "win-groupheadercontainer")); LiveUnit.Assert.areEqual(1, header.childElementCount); var itemNode = header.firstElementChild; LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(itemNode, "win-groupheader")); LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(itemNode, "myVVTestClass")); }); }; var list = new WinJS.Binding.List(initData()); var myGroupedList = list.createGrouped(groupKey, groupData); var listView = new ListView(placeholder, { itemDataSource: myGroupedList.dataSource, groupDataSource: myGroupedList.groups.dataSource, itemTemplate: generateRenderer("100px"), groupHeaderTemplate: generateRenderer("50px", "Header"), layout: { initialize: function (site, groups) { layoutSite = site; return "vertical"; }, layout: function (tree) { return testSite(tree); } } }); return Helper.ListView.waitForReady(listView)().then(function () { placeholder.parentNode.removeChild(placeholder); complete(); }); }; testTreeUpdate = function (complete) { var layout = { initialize: function (site, groups) { return "vertical"; } }; verifyTreeUpdate(layout, validateFlatTree, complete); }; testTreeUpdateWithStructureNodes = function (complete) { var layout = { initialize: function (site, groups) { return "vertical"; }, numberOfItemsPerItemsBlock: STRUCTURENODE_SIZE }; verifyTreeUpdate(layout, function (listView) { validateFlatTree(listView); }, complete); }; testGroupedTreeUpdate = function (complete) { var layout = { initialize: function (site, groups) { return "vertical"; } }; verifyGroupedTreeUpdate(layout, validateGroupedTree, complete); }; testGroupedTreeUpdateWithStructureNodes = function (complete) { var layout = { initialize: function (site, groups) { return "vertical"; }, numberOfItemsPerItemsBlock: STRUCTURENODE_SIZE }; verifyGroupedTreeUpdate(layout, function (listView) { validateGroupedTree(listView); }, complete); }; testNoReparentingDuringEdits = function (complete) { var placeholder; function setupListView(realizedRangeStart, groups?) { if (placeholder) { placeholder.parentNode.removeChild(placeholder); } placeholder = createListViewElement(); placeholder.addEventListener("contentanimating", function (eventObject) { eventObject.preventDefault(); }); var layout = { initialize: function (site, groups) { return "vertical"; }, itemsFromRange: function (start, end) { return { firstIndex: realizedRangeStart, lastIndex: realizedRangeStart + 10 }; }, }; var list = new WinJS.Binding.List(initData()), listView: WinJS.UI.PrivateListView<{}>; if (groups) { var groupedList = list.createGrouped(groupKey, groupData), listView = new ListView(placeholder, { itemDataSource: groupedList.dataSource, groupDataSource: groupedList.groups.dataSource, itemTemplate: generateRenderer("50px"), groupHeaderTemplate: generateRenderer("50px"), layout: layout }); } else { listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: generateRenderer("50px"), layout: layout }); } return Helper.ListView.waitForReady(listView)().then(function () { var containers = placeholder.querySelectorAll(".win-container"); for (var i = 0, len = containers.length; i < len; i++) { var container = containers[i]; container.winTestSign = i; } return listView; }); } function verifyContainers(startFrom, exepected) { var containers = placeholder.querySelectorAll(".win-container"), current = []; for (var i = 0, len = containers.length; i < len; i++) { var id = containers[i].winTestSign; current.push(id === +id ? "" + id : "New"); } for (i = 0, len = exepected.length; i < len; i++) { LiveUnit.Assert.areEqual(exepected[i], current[startFrom + i]); } } setupListView(0).then(function (listView) { LiveUnit.LoggingCore.logComment("single removal at the begining"); listView.itemDataSource.list.shift(); return Helper.ListView.waitForReady(listView, -1)(listView); }).then(function (listView) { verifyContainers(0, ["1", "2", "3", "4"]); return setupListView(0); }).then(function (listView) { LiveUnit.LoggingCore.logComment("single removal at the end"); listView.itemDataSource.list.pop(); return Helper.ListView.waitForReady(listView, -1)(listView); }).then(function (listView) { verifyContainers(0, ["0", "1", "2", "3"]); return setupListView(0); }).then(function (listView) { LiveUnit.LoggingCore.logComment("multiple removals in the middle"); listView.itemDataSource.list.splice(2, 1); listView.itemDataSource.list.splice(0, 1); listView.itemDataSource.list.splice(4, 1); return Helper.ListView.waitForReady(listView, -1)(listView); }).then(function (listView) { verifyContainers(0, ["1", "3", "4", "5", "7"]); return setupListView(10); }).then(function (listView) { LiveUnit.LoggingCore.logComment("removal before the realized range"); listView.itemDataSource.list.shift(); return Helper.ListView.waitForReady(listView, -1)(listView); }).then(function (listView) { verifyContainers(10, ["11", "12", "13", "14"]); return setupListView(15, true); }).then(function (listView) { LiveUnit.LoggingCore.logComment("group shift + removal before the realized range + removal in the realized range"); listView.itemDataSource.list.splice(0, 1); listView.itemDataSource.list.splice(10, 1); listView.itemDataSource.list.splice(15, 1); listView.itemDataSource.list.splice(17, 1); return Helper.ListView.waitForReady(listView, -1)(listView); }).then(function (listView) { verifyContainers(15, ["18", "19", "21", "22"]); return setupListView(0); }).then(function (listView) { LiveUnit.LoggingCore.logComment("single insert"); listView.itemDataSource.list.splice(2, 0, { title: "New" }); return Helper.ListView.waitForReady(listView, -1)(listView); }).then(function (listView) { verifyContainers(0, ["0", "1", "New", "2", "3"]); return setupListView(0, true); }).then(function (listView) { LiveUnit.LoggingCore.logComment("single insert at the end of group"); listView.itemDataSource.list.splice(10, 0, { title: "New", group: 0 }); return Helper.ListView.waitForReady(listView, -1)(listView); }).then(function (listView) { verifyContainers(0, ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "New"]); return setupListView(0); }).then(function (listView) { LiveUnit.LoggingCore.logComment("multiple inserts"); listView.itemDataSource.list.splice(2, 0, { title: "New" }); listView.itemDataSource.list.splice(0, 0, { title: "New" }); listView.itemDataSource.list.splice(6, 0, { title: "New" }); return Helper.ListView.waitForReady(listView, -1)(listView); }).then(function (listView) { verifyContainers(0, ["New", "0", "1", "New", "2", "3", "New", "4", "5", "6"]); return setupListView(0); }).then(function (listView) { LiveUnit.LoggingCore.logComment("mix of removals and inserts"); listView.itemDataSource.list.splice(2, 1, { title: "New" }); listView.itemDataSource.list.splice(0, 1); listView.itemDataSource.list.splice(4, 0, { title: "New" }); listView.itemDataSource.list.splice(5, 2); return Helper.ListView.waitForReady(listView, -1)(listView); }).then(function (listView) { verifyContainers(0, ["1", "New", "3", "4", "New", "7", "8"]); return setupListView(10); }).then(function (listView) { LiveUnit.LoggingCore.logComment("inserts before the realzied range"); listView.itemDataSource.list.unshift({ title: "New" }); listView.itemDataSource.list.unshift({ title: "New" }); return Helper.ListView.waitForReady(listView, -1)(listView); }).then(function (listView) { verifyContainers(10, ["8", "9", "10", "11"]); return setupListView(10); }).then(function (listView) { LiveUnit.LoggingCore.logComment("mix of changes before the realized range and in the realized range"); listView.itemDataSource.list.unshift({ title: "New" }); listView.itemDataSource.list.splice(12, 1); listView.itemDataSource.list.splice(16, 0, { title: "New" }); return Helper.ListView.waitForReady(listView, -1)(listView); }).then(function (listView) { verifyContainers(10, ["9", "10", "12", "13", "14", "15", "New"]); return setupListView(15, true); }).then(function (listView) { LiveUnit.LoggingCore.logComment("group shift + insert before the realized range + insert in the realized range"); listView.itemDataSource.list.splice(0, 0, { title: "New", group: 0 }); listView.itemDataSource.list.splice(11, 0, { title: "New", group: 1 }); listView.itemDataSource.list.splice(18, 0, { title: "New", group: 1 }); return Helper.ListView.waitForReady(listView, -1)(listView); }).then(function (listView) { verifyContainers(15, ["13", "14", "15", "New", "16"]); placeholder.parentNode.removeChild(placeholder); complete(); }); }; testRebuildingStructureNodesAfterResize = function (complete) { WinJS.UI._VirtualizeContentsView._chunkSize = 15; WinJS.UI._VirtualizeContentsView._maxTimePerCreateContainers = Number.MAX_VALUE; var placeholder = createListViewElement("330px"); placeholder.id = "SimpleFlexBasedLayout"; var expectedItemsPerBlock = 6; var list = new WinJS.Binding.List(initData(30)), groupedList = list.createGrouped(groupKey, groupData), listView = new ListView(placeholder, { itemDataSource: groupedList.dataSource, groupDataSource: groupedList.groups.dataSource, itemTemplate: generateRenderer("50px"), groupHeaderTemplate: generateRenderer("50px"), layout: Object.create({ initialize: function (site, groups) { this.site = site; WinJS.Utilities.addClass(this.site.surface, "SimpleFlexBasedLayoutSurface"); return "horizontal"; }, layout: function (tree) { for (var g = 0; g < tree.length; g++) { var group = tree[g]; group.header.style.display = "none"; WinJS.Utilities.addClass(group.itemsContainer.element, "SimpleFlexBasedLayout"); var blocks = group.itemsContainer.itemsBlocks; for (var b = 0; b < blocks.length; b++) { var block = blocks[b]; WinJS.Utilities.addClass(block.element, "SimpleFlexBasedLayout"); LiveUnit.Assert.isTrue((b + 1) === blocks.length || (block.items.length === expectedItemsPerBlock)); } } } }, { numberOfItemsPerItemsBlock: { get: function () { var retVal = Math.floor(this.site.viewportSize.height / 50); LiveUnit.Assert.areEqual(expectedItemsPerBlock, retVal); return retVal; } } }) }); Helper.ListView.waitForReady(listView)().then(function () { validateGroupedTree(listView, expectedItemsPerBlock); listView._raiseViewLoading(); // Trigger resize handler placeholder.style.height = "530px"; expectedItemsPerBlock = 10; return Helper.ListView.waitForReady(listView, 50)(); }).then(function () { validateGroupedTree(listView, expectedItemsPerBlock); listView._raiseViewLoading(); // Trigger resize handler placeholder.style.height = "430px"; expectedItemsPerBlock = 8; return Helper.ListView.waitForReady(listView, 50)(); }).done(function () { validateGroupedTree(listView, expectedItemsPerBlock); placeholder.parentNode.removeChild(placeholder); complete(); }); }; testResizeDuringLazyTreeCreation = function (complete) { WinJS.UI._VirtualizeContentsView._maxTimePerCreateContainers = 0; WinJS.UI._VirtualizeContentsView._createContainersJobTimeslice = 0; var list = new WinJS.Binding.List(initData(100, 20)); var myGroupedList = list.createGrouped(groupKey, groupData); var placeholder = createListViewElement("180px"); placeholder.style.width = "1200px"; var listView = new ListView(placeholder, { itemDataSource: myGroupedList.dataSource, groupDataSource: myGroupedList.groups.dataSource, itemTemplate: function (itemPromise) { return itemPromise.then(function (item) { var element = document.createElement("div"); element.className = "myVVTestClass"; element.textContent = item.data.title; element.style.width = "50px"; element.style.height = "100px"; element.style.backgroundColor = "blue"; return element; }); }, groupHeaderTemplate: generateRenderer("50px", "Header"), layout: new WinJS.UI.GridLayout() }); if (!listView.layout['_usingStructuralNodes']) { placeholder.parentNode.removeChild(placeholder); complete(); return; } var jobNode; listView._view._scheduleLazyTreeCreation = function () { jobNode = Object.getPrototypeOf(listView._view)._scheduleLazyTreeCreation.call(listView._view); jobNode.pause(); return jobNode; }; return Helper.ListView.waitForReady(listView)().then(function () { LiveUnit.Assert.areEqual(1, listView._view.tree.length); var itemsContainer = listView._view.tree[0].itemsContainer; LiveUnit.Assert.areEqual(5, itemsContainer.itemsBlocks.length); for (var i = 0; i < itemsContainer.itemsBlocks.length; i++) { LiveUnit.Assert.areEqual(WinJS.UI._LayoutCommon._barsPerItemsBlock, itemsContainer.itemsBlocks[i].items.length); } // Trigger resize handler placeholder.style.height = "280px"; listView._raiseViewLoading(); return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { LiveUnit.Assert.areEqual(1, listView._view.tree.length); var itemsContainer = listView._view.tree[0].itemsContainer; LiveUnit.Assert.areEqual(3, itemsContainer.itemsBlocks.length); LiveUnit.Assert.areEqual(WinJS.UI._LayoutCommon._barsPerItemsBlock * 2, itemsContainer.itemsBlocks[0].items.length); LiveUnit.Assert.areEqual(20 - 2 * WinJS.UI._LayoutCommon._barsPerItemsBlock * 2, itemsContainer.itemsBlocks[2].items.length); jobNode.resume(); return Helper.ListView.waitForAllContainers(listView); }).done(function () { validateGroupedTree(listView, WinJS.UI._LayoutCommon._barsPerItemsBlock * 2); placeholder.parentNode.removeChild(placeholder); complete(); }); }; // Verifies that structural nodes are enabled/disabled on the fly due to list edits. // When all of the groups are uniform, structural nodes should be enabled. // If any of the groups are cell spanning, structural nodes should be disabled. testTogglingStructuralNodesDueToEdits = function (complete) { function itemInfo(index) { return { width: 95, height: 95 }; } function groupInfo(group) { if (group.index <= 1) { return { enableCellSpanning: false }; } else { return { enableCellSpanning: true, cellWidth: 95, cellHeight: 95 }; } } function verifyStructuralNodesDisabled() { validateGroupedTree(listView); LiveUnit.Assert.areEqual(0, listView.element.querySelectorAll("." + WinJS.UI._itemsBlockClass).length, "There shouldn't be any items blocks"); LiveUnit.Assert.isFalse(WinJS.Utilities.hasClass(listView._canvas, WinJS.UI._structuralNodesClass), "The surface should not have the structuralnodes CSS class"); } var list = new WinJS.Binding.List(initData(150, 50)).createGrouped(groupKey, groupData), placeholder = createListViewElement(), extraItems, listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: generateRenderer("90px"), groupDataSource: list.groups.dataSource, groupHeaderTemplate: generateRenderer("10px"), layout: { type: WinJS.UI.CellSpanningLayout, groupInfo: groupInfo, itemInfo: itemInfo } }), tests = [ function () { // We should have 2 uniform groups and 1 cell spanning group so structural nodes should be disabled verifyStructuralNodesDisabled(); extraItems = list.splice(100, 50); return true; }, function () { // Now we have 2 groups (both uniform) so structural nodes should be enabled validateGroupedTree(listView, 12); LiveUnit.Assert.isTrue(WinJS.Utilities.hasClass(listView._canvas, WinJS.UI._structuralNodesClass), "The surface should have the structuralnodes CSS class"); extraItems.forEach(function (item) { list.push(item); }); return true; }, function () { // We're back to 2 uniform groups and 1 cell spanning group so structural nodes should be disabled verifyStructuralNodesDisabled(); placeholder.parentNode.removeChild(placeholder); complete(); } ]; if (!listView.layout['_usingStructuralNodes']) { placeholder.parentNode.removeChild(placeholder); complete(); return; } Helper.ListView.runTests(listView, tests); }; // Verifies that when the ListView is resized such that the number of items per // column changes, columns are not split across multiple items blocks. testResizeWithStructuralNodes = function (complete) { function verifyTree() { LiveUnit.Assert.isTrue(placeholder.querySelectorAll("." + WinJS.UI._itemsBlockClass).length > 1, "ListView must be using more than 1 items block in order for this test to verify that " + "columns do not get split across items blocks during resize"); validateFlatTree(listView, WinJS.UI._LayoutCommon._barsPerItemsBlock * itemsPerColumn); } var list = new WinJS.Binding.List(initData(100)), placeholder = createListViewElement("300px"), listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: generateRenderer("100px") }), // listView height = 300px, container height = 100px itemsPerColumn = 3, tests = [ function () { verifyTree(); // Trigger resize handler placeholder.style.height = "400px"; // listView height = 400px, container height = 100px itemsPerColumn = 4; return true; }, function () { verifyTree(); placeholder.parentNode.removeChild(placeholder); complete(); } ]; if (!listView.layout['_usingStructuralNodes']) { placeholder.parentNode.removeChild(placeholder); complete(); return; } Helper.ListView.runTests(listView, tests); }; testLazyFlatTreeCreation = function (complete) { WinJS.UI._VirtualizeContentsView._maxTimePerCreateContainers = 0; WinJS.UI._VirtualizeContentsView._disableCustomPagesPrefetch = true; var placeholder = createListViewElement(); var stateChangeCounter = { itemsLoading: 0, viewPortLoaded: 0, itemsLoaded: 0, complete: 0 }; placeholder.addEventListener("loadingstatechanged", function (eventObject) { stateChangeCounter[eventObject.target['winControl'].loadingState]++; }); var itemsCount = BIG_DATASET, list = new WinJS.Binding.List(initData(itemsCount)); var listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: generateRenderer("100px"), layout: new WinJS.UI.ListLayout() }); return Helper.ListView.waitForReady(listView)().then(function () { LiveUnit.Assert.areEqual(1, stateChangeCounter.viewPortLoaded); LiveUnit.Assert.areEqual(1, stateChangeCounter.itemsLoaded); LiveUnit.Assert.areEqual(1, stateChangeCounter.complete); LiveUnit.Assert.areEqual(9, placeholder.querySelectorAll(".win-item").length); return listView._view._creatingContainersWork ? listView._view._creatingContainersWork.promise : null; }).then(function () { validateFlatTree(listView, ListLayout._numberOfItemsPerItemsBlock); LiveUnit.Assert.areEqual(1, stateChangeCounter.viewPortLoaded); LiveUnit.Assert.areEqual(1, stateChangeCounter.itemsLoaded); LiveUnit.Assert.areEqual(1, stateChangeCounter.complete); placeholder.parentNode.removeChild(placeholder); complete(); }); }; testLazyTreeCreationPriority = function (complete) { WinJS.UI._VirtualizeContentsView._maxTimePerCreateContainers = 0; WinJS.UI._VirtualizeContentsView._disableCustomPagesPrefetch = true; var placeholder = createListViewElement(); var stateChangeCounter = { itemsLoading: 0, viewPortLoaded: 0, itemsLoaded: 0, complete: 0 }; placeholder.addEventListener("loadingstatechanged", function (eventObject) { stateChangeCounter[eventObject.target['winControl'].loadingState]++; }); var itemsCount = BIG_DATASET, list = new WinJS.Binding.List(initData(itemsCount)); var listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: generateRenderer("100px"), layout: new WinJS.UI.ListLayout() }); var jobNode; listView._view._scheduleLazyTreeCreation = function () { jobNode = Object.getPrototypeOf(listView._view)._scheduleLazyTreeCreation.call(listView._view); jobNode.pause(); return jobNode; }; var afterIdle; return Helper.ListView.waitForReady(listView)().then(function () { LiveUnit.Assert.areEqual(1, stateChangeCounter.viewPortLoaded); LiveUnit.Assert.areEqual(1, stateChangeCounter.itemsLoaded); LiveUnit.Assert.areEqual(1, stateChangeCounter.complete); LiveUnit.Assert.areEqual(9, placeholder.querySelectorAll(".win-item").length); LiveUnit.Assert.isTrue(placeholder.querySelectorAll(".win-container").length < BIG_DATASET); var containers = placeholder.querySelectorAll(".win-container").length; WinJS.Utilities.Scheduler.schedule(function () { LiveUnit.Assert.areEqual(containers, placeholder.querySelectorAll(".win-container").length); }, WinJS.Utilities.Scheduler.Priority.idle + 1, null, "test before idle"); WinJS.Utilities.Scheduler.schedule(function () { afterIdle = true; }, WinJS.Utilities.Scheduler.Priority.idle - 1, null, "test after idle"); jobNode.resume(); return listView._view._creatingContainersWork ? listView._view._creatingContainersWork.promise : null; }).then(function () { validateFlatTree(listView, ListLayout._numberOfItemsPerItemsBlock); LiveUnit.Assert.isFalse(afterIdle); LiveUnit.Assert.areEqual(1, stateChangeCounter.viewPortLoaded); LiveUnit.Assert.areEqual(1, stateChangeCounter.itemsLoaded); LiveUnit.Assert.areEqual(1, stateChangeCounter.complete); placeholder.parentNode.removeChild(placeholder); complete(); }); }; testLazyGroupedTreeCreationWithSmallGroups = function (complete) { var itemsCount = BIG_DATASET, data = initData(itemsCount, 250); testLazyGroupedTreeCreation(data, complete); }; testLazyGroupedTreeCreationWithBigGroups = function (complete) { var itemsCount = BIG_DATASET, data = initData(itemsCount, 2250); testLazyGroupedTreeCreation(data, complete); }; testStoppingLazyTreeCreation = function (complete) { WinJS.UI._VirtualizeContentsView._maxTimePerCreateContainers = 0; function createListView(oldPlaceholder?) { if (oldPlaceholder) { oldPlaceholder.winControl._dispose(); oldPlaceholder.parentNode.removeChild(oldPlaceholder); } var placeholder = createListViewElement(); var state = { canceled: false, }; var listView = new ListView(placeholder, { itemDataSource: (new WinJS.Binding.List(initData(BIG_DATASET))).dataSource, itemTemplate: generateRenderer("100px"), layout: new WinJS.UI.ListLayout() }); listView._view._scheduleLazyTreeCreation = function () { var jobNode = Object.getPrototypeOf(listView._view)._scheduleLazyTreeCreation.call(listView._view); jobNode.cancel = function () { state.canceled = true; Object.getPrototypeOf(jobNode).cancel.call(jobNode); }; return jobNode; }; return new WinJS.Promise(function (c, e, p) { WinJS.Utilities.Scheduler.schedule(c, WinJS.Utilities.Scheduler.Priority.high); }).then(function () { LiveUnit.Assert.isFalse(state.canceled); return { listView: listView, state: state }; }); } createListView().then(function (result) { var element = result.listView.element; element.parentNode.removeChild(element); result.listView._dispose(); LiveUnit.Assert.isTrue(result.state.canceled); return createListView(); }).then(function (result) { result.listView.itemDataSource = (new WinJS.Binding.List(initData())).dataSource; LiveUnit.Assert.isTrue(result.state.canceled); return createListView(result.listView.element); }).then(function (result) { result.listView.itemTemplate = generateRenderer("50px"); LiveUnit.Assert.isTrue(result.state.canceled); return createListView(result.listView.element); }).then(function (result) { var list = result.listView.itemDataSource.list; list.shift(); LiveUnit.Assert.isFalse(result.state.canceled); result.listView.element.style.width = "600px"; LiveUnit.Assert.isFalse(result.state.canceled); var containerPromise = (result.listView._view._creatingContainersWork ? result.listView._view._creatingContainersWork.promise : WinJS.Promise.wrap()); return WinJS.Promise.join([containerPromise, Helper.ListView.waitForReady(result.listView)()]).then(function () { return result; }); }).then(function (result) { LiveUnit.Assert.isFalse(result.state.canceled); validateFlatTree(result.listView, ListLayout._numberOfItemsPerItemsBlock); result.listView.element.parentNode.removeChild(result.listView.element); result.listView._dispose(); complete(); }); }; testEditsDuringLazyCreation = function (complete) { WinJS.UI._VirtualizeContentsView._maxTimePerCreateContainers = 0; var itemsCount = BIG_DATASET, data = initData(itemsCount, 250); var placeholder = createListViewElement(); var list = new WinJS.Binding.List(data), groupedList = list.createGrouped(groupKey, groupData); var listView = new ListView(placeholder, { itemDataSource: groupedList.dataSource, groupDataSource: groupedList.groups.dataSource, itemTemplate: generateRenderer("100px"), groupHeaderTemplate: generateRenderer("50px"), layout: new WinJS.UI.ListLayout() }); listView._view._scheduleLazyTreeCreation = function () { WinJS.Utilities._setImmediate(function () { listView.itemDataSource.beginEdits(); listView.itemDataSource.list.splice(10, 10); listView.itemDataSource.list.shift(); WinJS.Promise.timeout(250).then(function () { listView.itemDataSource.endEdits(); }); }); return Object.getPrototypeOf(listView._view)._scheduleLazyTreeCreation.call(listView._view); }; return Helper.ListView.waitForReady(listView)().then(function () { return listView._view._creatingContainersWork ? listView._view._creatingContainersWork.promise : null; }).then(function () { validateGroupedTree(listView, ListLayout._numberOfItemsPerItemsBlock); placeholder.parentNode.removeChild(placeholder); complete(); }); }; // Verifies that if the data in the ListView's Binding.List is entirely replaced while // containers are still being created, the ListView will create enough containers for // the edits to fulfill its contract with the animation system (each modified item needs // a container). // A specific pattern that some apps use which hits this is as follows: // - ListView is initialized with local data and a fetch is made to get the latest data from // the server. // - While the ListView is lazily creating containers, the server responds. The app replaces the // local data in the ListView's data source with the data from the server. Specifically, this // can be done by setting the Binding.List's length to 0 and pushing on the data from the server. // Regression test for #146 (https://github.com/winjs/winjs/issues/146#issuecomment-41634154) testReplaceDataDuringLazyContainerCreation = function (complete) { Helper.initUnhandledErrors(); WinJS.UI._VirtualizeContentsView._chunkSize = 50; var itemsCount = 75; var data = initData(itemsCount); var list = new WinJS.Binding.List(data); var replaceData = function () { // Simulates an app receiving a fresh copy of the data from the server // and swapping the local data with the server's data. list.length = 0; data.forEach(function (item) { list.push(item); }); }; var placeholder = createListViewElement(); var listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: generateRenderer("100px") }); listView._view._createChunkWithBlocks = function (groups, count, blockSize, chunkSize) { Object.getPrototypeOf(listView._view)._createChunkWithBlocks.call(listView._view, groups, count, blockSize, chunkSize); if (listView._view.containers.length >= 50) { return true; } }; listView._view._createChunk = function (groups, count, chunkSize) { Object.getPrototypeOf(listView._view)._createChunk.call(listView._view, groups, count, chunkSize); if (listView._view.containers.length >= 50) { return true; } }; var jobNode; listView._view._scheduleLazyTreeCreation = function () { jobNode = Object.getPrototypeOf(listView._view)._scheduleLazyTreeCreation.call(listView._view); jobNode.pause(); return jobNode; }; return Helper.ListView.waitForReady(listView)().then(function () { LiveUnit.Assert.areEqual(50, listView._view.containers.length); replaceData(); jobNode.resume(); return Helper.ListView.waitForAllContainers(listView); }).then(function () { LiveUnit.Assert.areEqual(75, listView._view.containers.length); return Helper.validateUnhandledErrorsOnIdle(); }).then(complete); }; // Verifies that all items in a CellSpanningLayout get properly laid out when // insertions occur before all of the containers have been created. // Regression test for WinBlue#427057. testInsertionsDuringLazyContainerCreation = function (complete) { if (!Helper.Browser.supportsCSSGrid) { complete(); return; } WinJS.UI._VirtualizeContentsView._maxTimePerCreateContainers = 0; Helper.initUnhandledErrors(); function verifyTile(data) { var container = element.querySelectorAll(".win-container")[data.index]; LiveUnit.Assert.areEqual(data.left, container.offsetLeft, "win-container has unexpected offsetLeft"); LiveUnit.Assert.areEqual(data.top, container.offsetTop, "win-container has unexpected offsetTop"); LiveUnit.Assert.areEqual(data.width, container.offsetWidth, "win-container has unexpected offsetWidth"); LiveUnit.Assert.areEqual(data.height, container.offsetHeight, "win-container has unexpected offsetHeight"); } function itemInfo() { return { width: 100, height: 100 }; } function groupKey(item) { return item.group; } function groupData(item) { return { title: groupKey(item) }; } var items = []; items[0] = { title: "Tile 0", group: "A" }; for (var i = 1; i < 100; i++) { items[i] = { title: "Tile " + i, group: "B" }; } var element = createListViewElement(); var list = new WinJS.Binding.List(items).createGrouped(groupKey, groupData); var listView = new ListView(element, { itemDataSource: list.dataSource, itemTemplate: generateRenderer("100px"), groupDataSource: list.groups.dataSource, groupHeaderTemplate: generateRenderer("50px"), layout: { type: WinJS.UI.CellSpanningLayout, groupInfo: { enableCellSpanning: true, cellWidth: 100, cellHeight: 100 }, itemInfo: itemInfo } }); var origLayout = listView.layout.layout; listView.layout.layout = function () { listView.layout.layout = origLayout; var result = listView.layout.layout.apply(this, arguments); var layoutComplete = WinJS.Promise.as(result.layoutComplete ? result.layoutComplete : result); layoutComplete.then(function () { // Insert some data after the first layout pass WinJS.Utilities.Scheduler.schedule(function () { listView.itemDataSource.list.push({ title: "New Tile 0", group: "B" }); listView.itemDataSource.list.push({ title: "New Tile 1", group: "B" }); listView.itemDataSource.list.push({ title: "New Tile 2", group: "B" }); listView.itemDataSource.list.push({ title: "New Tile 3", group: "B" }); Helper.ListView.waitForAllContainers(listView).then(function () { var headerContainerHeight = 70; var containerCount = element.querySelectorAll(".win-container").length; var itemsContainer = element.querySelectorAll(".win-itemscontainer")[1]; // Verify lay out of first group verifyTile({ index: 0, left: 0, top: headerContainerHeight, width: 100, height: 100 }); // Verify lay out of second group for (var i = 1; i < containerCount; i++) { var col = Math.floor((i - 1) / 2); var row = (i - 1) % 2; verifyTile({ index: i, left: itemsContainer.offsetLeft + 100 * col, top: headerContainerHeight + 100 * row, width: 100, height: 100 }); } element.parentNode.removeChild(element); return Helper.validateUnhandledErrorsOnIdle(); }).done(complete); }); }); return result; }; }; testRealizeMoreThanCreated = function (complete) { // ListView height is 300, container height is 30 var itemsPerColumn = 10, numberOfItemsPerItemsBlock = itemsPerColumn * WinJS.UI._LayoutCommon._barsPerItemsBlock; // For the purposes of this test, _chunkSize should be a multiple of numberOfItemsPerItemsBlock WinJS.UI._VirtualizeContentsView._chunkSize = numberOfItemsPerItemsBlock; WinJS.UI._VirtualizeContentsView._maxTimePerCreateContainers = 0; var itemsCount = BIG_DATASET, list = new WinJS.Binding.List(initData(itemsCount)); var placeholder = createListViewElement(); var listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: generateRenderer("30px"), layout: new WinJS.UI.GridLayout() }); var jobNode; listView._view._scheduleLazyTreeCreation = function () { jobNode = Object.getPrototypeOf(listView._view)._scheduleLazyTreeCreation.call(listView._view); jobNode.pause(); return jobNode; }; return Helper.ListView.waitForReady(listView)().then(function () { LiveUnit.Assert.areEqual(0, listView.indexOfFirstVisible); LiveUnit.Assert.areEqual(WinJS.UI._VirtualizeContentsView._chunkSize - 1, listView.indexOfLastVisible); WinJS.UI._VirtualizeContentsView._chunkSize = defaultChunkSize; WinJS.UI._VirtualizeContentsView._maxTimePerCreateContainers = defaultMaxTime; jobNode.resume(); return Helper.ListView.waitForReady(listView, 100)(); }).then(function () { LiveUnit.Assert.areEqual(0, listView.indexOfFirstVisible); LiveUnit.Assert.areEqual(99, listView.indexOfLastVisible); placeholder.parentNode.removeChild(placeholder); complete(); }); }; testScrollingDuringLazyCreation = function (complete) { // ListView height is 300, container height is 100 var itemsPerColumn = 3, numberOfItemsPerItemsBlock = itemsPerColumn * WinJS.UI._LayoutCommon._barsPerItemsBlock; // For the purposes of this test, _chunkSize should be a multiple of numberOfItemsPerItemsBlock WinJS.UI._VirtualizeContentsView._chunkSize = 41 * numberOfItemsPerItemsBlock; WinJS.UI._VirtualizeContentsView._startupChunkSize = WinJS.UI._VirtualizeContentsView._chunkSize; WinJS.UI._VirtualizeContentsView._maxTimePerCreateContainers = 0; function checkTile(listView, index) { var element = listView.elementFromIndex(index); LiveUnit.Assert.areEqual("Tile" + index, element.textContent); } function createEvent(key, element) { return { keyCode: key, target: element, stopPropagation: function () { }, preventDefault: function () { } }; } var itemsCount = BIG_DATASET, list = new WinJS.Binding.List(initData(itemsCount)); var placeholder = createListViewElement(); var listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: generateRenderer("100px"), layout: new WinJS.UI.GridLayout() }); var jobNode; listView._view._scheduleLazyTreeCreation = function () { jobNode = Object.getPrototypeOf(listView._view)._scheduleLazyTreeCreation.call(listView._view); jobNode.pause(); return jobNode; }; return Helper.ListView.waitForReady(listView)().then(function () { listView.indexOfFirstVisible = 99; return Helper.ListView.waitForReady(listView)(); }).then(function () { LiveUnit.Assert.areEqual(99, listView.indexOfFirstVisible); LiveUnit.Assert.areEqual(3300, listView.scrollPosition); checkTile(listView, 99); listView._currentMode().onKeyDown(createEvent(WinJS.Utilities.Key.end, listView.elementFromIndex(100))); return Helper.ListView.waitForReady(listView)(); }).then(function () { LiveUnit.Assert.areEqual(WinJS.UI._VirtualizeContentsView._chunkSize - 1, listView.indexOfLastVisible); listView.indexOfFirstVisible = 0; return Helper.ListView.waitForReady(listView)(); }).then(function () { LiveUnit.Assert.areEqual(0, listView.indexOfFirstVisible); listView.indexOfFirstVisible = 10000; jobNode.resume(); return Helper.ListView.waitForReady(listView)(); }).then(function () { return listView._view._creatingContainersWork ? listView._view._creatingContainersWork.promise : null; }).then(function () { return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { LiveUnit.Assert.areEqual(9999, listView.indexOfFirstVisible); placeholder.parentNode.removeChild(placeholder); complete(); }); }; testAnimationsInterface = function (complete) { function initModifiedArray(count) { var retVal = []; for (var i = 0; i < count; i++) { retVal.push({ oldIndex: i, newIndex: i, }); } return retVal; } function spliceModifiedArray(array, index, removedCount, newItem?) { var i, entry; for (i = 0; i < removedCount; i++) { array[index + i].newIndex = -1; } for (i = index + removedCount; i < array.length; i++) { entry = array[i]; if (entry.newIndex !== -1) { entry.newIndex -= removedCount; } } if (newItem) { array.splice(index, 0, { oldIndex: -1, newIndex: -1 }); for (i = index + 1; i < array.length; i++) { entry = array[i]; if (entry.newIndex !== -1) { entry.newIndex++; } } } } function updateModifiedArray(array) { for (var i = 0; i < array.length; i++) { var entry = array[i]; if (entry.oldIndex === -1 && entry.newIndex === -1) { entry.newIndex = i; } } } function compareModifiedArray(left, right) { function compareArrays(expected, actual) { LiveUnit.Assert.areEqual(expected.length, actual.length); for (var i = 0; i < expected.length; i++) { LiveUnit.Assert.areEqual(JSON.stringify(expected[i]), JSON.stringify(actual[i])); } } function getRemoved(source) { return source.filter(function (item) { return item.newIndex === -1; }).map(function (item) { return item.oldIndex; }).sort(); } function getInserted(source) { return source.filter(function (item) { return item.oldIndex === -1; }).map(function (item) { return item.newIndex; }).sort(); } function getMoved(source) { return source.filter(function (item) { return item.oldIndex !== -1 && item.newIndex !== -1; }).map(function (item) { return { oldIndex: item.oldIndex, newIndex: item.newIndex }; }).sort(function (left, right) { return right.oldIndex - left.oldIndex; }); } compareArrays(getRemoved(left), getRemoved(right)); compareArrays(getInserted(left), getInserted(right)); compareArrays(getMoved(left), getMoved(right)); } var placeholder = createListViewElement(); placeholder.style.height = "900px"; var itemsCount = 30, data = initData(itemsCount), list = new WinJS.Binding.List(data), groupedList = list.createGrouped(groupKey, groupData); var changes = false, modifiedElementsPattern, modifiedGroupsPattern, callCounter = 0, animationSignal = new WinJS._Signal(); var listView = new ListView(placeholder, { itemDataSource: groupedList.dataSource, groupDataSource: groupedList.groups.dataSource, itemTemplate: generateRenderer("40px"), groupHeaderTemplate: generateRenderer("50px"), layout: { initialize: function (site, groups) { this.site = site; return "vertical"; }, setupAnimations: function () { LiveUnit.Assert.areEqual(0, callCounter++); LiveUnit.Assert.areEqual(itemsCount, listView.element.querySelectorAll(".win-container").length); }, layout: function (tree, changedRange, modifiedElements, modifiedGroups) { if (modifiedElements.length || modifiedGroups.length) { LiveUnit.Assert.areEqual(1, callCounter++); LiveUnit.Assert.areEqual(list.length, listView.element.querySelectorAll(".win-container").length); compareModifiedArray(modifiedElementsPattern, modifiedElements); compareModifiedArray(modifiedGroupsPattern, modifiedGroups); } }, executeAnimations: function () { LiveUnit.Assert.areEqual(2, callCounter++); return animationSignal.promise; } } }); return Helper.ListView.waitForReady(listView)().then(function () { validateGroupedTree(listView); callCounter = 0; modifiedElementsPattern = initModifiedArray(list.length); modifiedGroupsPattern = initModifiedArray(Math.ceil(list.length / 10)); list.splice(25, 1); spliceModifiedArray(modifiedElementsPattern, 25, 1); list.splice(10, 10); spliceModifiedArray(modifiedElementsPattern, 10, 10); spliceModifiedArray(modifiedGroupsPattern, 1, 1); list.splice(8, 0, { title: "N1", group: 0 }); spliceModifiedArray(modifiedElementsPattern, 8, 0, true); updateModifiedArray(modifiedElementsPattern); updateModifiedArray(modifiedGroupsPattern); return waitForItemsLoaded(listView); }).then(function () { // By this time, viewportloaded has been triggered, and these methods of the layout have already been called: // 1. setupAnimations // 2. layout // 3. executeAnimations LiveUnit.Assert.areEqual(3, callCounter); animationSignal.complete(); return Helper.ListView.waitForReady(listView)(); }).then(function () { validateGroupedTree(listView); callCounter = 0; itemsCount = list.length; modifiedElementsPattern = initModifiedArray(list.length); modifiedGroupsPattern = initModifiedArray(Math.ceil(list.length / 10)); list.splice(11, 0, { title: "N2", group: 1 }); spliceModifiedArray(modifiedElementsPattern, 11, 0, true); spliceModifiedArray(modifiedGroupsPattern, 1, 0, true); updateModifiedArray(modifiedElementsPattern); updateModifiedArray(modifiedGroupsPattern); return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { validateGroupedTree(listView); placeholder.parentNode.removeChild(placeholder); complete(); }); }; testSerializeAnimations = function (complete) { function verifyTile(listView, index, text) { var element = listView.elementFromIndex(index); LiveUnit.Assert.areEqual(text, element.textContent); } var placeholder = createListViewElement(); var data = initData(COUNT), list = new WinJS.Binding.List(data); var layoutExpected = true, animationsStarted = 0, animationSignal = new WinJS._Signal(); var listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: generateRenderer("40px"), layout: { initialize: function (site, groups) { return "vertical"; }, setupAnimations: function () { }, layout: function (tree, changedRange, modifiedElements, modifiedGroups) { LiveUnit.Assert.isTrue(layoutExpected); }, executeAnimations: function () { animationsStarted++; return animationSignal.promise; }, } }); return Helper.ListView.waitForReady(listView)().then(function () { validateFlatTree(listView); list.unshift({ title: "N0", group: 0 }); return waitForItemsLoaded(listView); }).then(function () { return WinJS.Promise.timeout(50); }).then(function () { LiveUnit.Assert.areEqual(1, animationsStarted); validateFlatTree(listView); verifyTile(listView, 0, "N0"); verifyTile(listView, 1, "Tile0"); layoutExpected = false; list.unshift({ title: "N1", group: 0 }); return WinJS.Promise.timeout(50); }).then(function () { LiveUnit.Assert.areEqual(1, animationsStarted); layoutExpected = true; animationSignal.complete(); return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { validateFlatTree(listView); verifyTile(listView, 0, "N1"); verifyTile(listView, 1, "N0"); verifyTile(listView, 2, "Tile0"); placeholder.parentNode.removeChild(placeholder); complete(); }); }; testChangedRange = function (complete) { var rangeTester = { expectedRange: function (start, end) { this.firstIndex = start; this.lastIndex = end; }, verify: function (range) { LiveUnit.Assert.areEqual(this.firstIndex, range.firstIndex); LiveUnit.Assert.areEqual(this.lastIndex, range.lastIndex); } } rangeTester.expectedRange(0, COUNT - 1); LiveUnit.LoggingCore.logComment("Initial AffectedRange is the entire list of data."); var placeholder = createListViewElement(); var data = initData(COUNT), list = new WinJS.Binding.List(data); var listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: generateRenderer("40px"), layout: { initialize: function (site, groups) { return "vertical"; }, layout: function (tree, range) { rangeTester.verify(range); }, } }); return Helper.ListView.waitForReady(listView)().then(function () { /*replace data source */ LiveUnit.LoggingCore.logComment("Test: replacing the data source sends a changedRange of the appropriateSize"); list.splice(list.length - 1, 1) // Remove and item from the end of the list. list = new WinJS.Binding.List(data); list.length = COUNT - 50; // shrink the size of the list. listView.itemDataSource = list.dataSource; rangeTester.expectedRange(0, list.length - 1); // Verify the range only goes as high as the last index in the new smaller datasource. return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { /* verify reload */ LiveUnit.LoggingCore.logComment("Test: reload, changedRange is the entire dataSource."); list.reverse(); rangeTester.expectedRange(0, list.length - 1); // Verify the range is the entire datasource. return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { placeholder.parentNode.removeChild(placeholder); complete(); }); }; testLayoutIsCalledForChangesToRealizedAndUnRealizedItems = function (complete) { LiveUnit.Assert.isTrue(COUNT >= 100, "Test expects a data set of at least 100 items.") var layoutTester = { testLayout: function () { LiveUnit.Assert.isTrue(layoutTester.layoutRan === true); layoutTester.layoutRan = false; }, layoutRan: false, } var placeholder = createListViewElement(); var data = initData(COUNT), list = new WinJS.Binding.List(data); var listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: generateRenderer("40px"), layout: { initialize: function (site, groups) { return "vertical"; }, layout: function (tree, range) { layoutTester.layoutRan = true; }, itemsFromRange: function (left, right) { return { firstIndex: 0, lastIndex: 9 }; } } }); return Helper.ListView.waitForReady(listView)().then(function () { /* move from 0 to 10 -> insert at 5 */ LiveUnit.LoggingCore.logComment("Test: move from 0 to 10 -> insert at 5"); // Move from Index 0 to index 10 list.move(0, 10); // [start= 0, end = 11) // Insert an item at index 5 list.splice(5, 0, { title: "N0", group: 0 }); // [start = 0, end = 12) return Helper.ListView.waitForReady(listView, -1)().then(layoutTester.testLayout); }).then(function () { /* change at 98 -> remove from 50 */ LiveUnit.LoggingCore.logComment("Test: change at 98 -> remove from 50"); // Change Item at index 98. list.setAt(98, { title: "N0", group: 0 }); // [start = 98, end = 99) // Remove item at index 50 list.splice(50, 1); // [start = 50, end = 98) return Helper.ListView.waitForReady(listView, -1)().then(layoutTester.testLayout); }).then(function () { /* insert -> move -> insert -> change -> remove -> remove -> remove -> remove -> move -> change -> insert -> insert -> insert -> insert */ LiveUnit.LoggingCore.logComment("Test: batch of 'insert -> move -> insert -> change -> remove -> remove -> remove -> remove -> move -> change -> insert -> insert -> insert -> insert' operations"); // Insert item at index 89 list.splice(89, 0, { title: "N0", group: 0 }); // [start = 89, end = 90) // Move item from index 90 to index 20 list.move(90, 20); // [start = 20, end = 90) // Insert item at index 10 list.splice(10, 0, { title: "N0", group: 0 }); // [start = 10, end = 91) // Change item at index 9 list.setAt(9, { title: "N0", group: 0 }); // [start = 9, end = 91) // Remove 2 items from index 11 list.splice(11, 1) // [start = 9, end = 90) list.splice(11, 1) // [start = 9, end = 89) // Remove 2 items from index 6 list.splice(6, 2) // [start = 6, end = 87) // Move item at index 7 to index 87 list.move(7, 22) // [start = 6, end = 88) // Change item at index 66 list.setAt(66, { title: "N0", group: 0 }); // [start = 6, end = 88) // Insert item at index 7 list.splice(7, 0, { title: "N0", group: 0 }) // [start = 6, end = 89) // Insert item at index 6 list.splice(6, 0, { title: "N0", group: 0 }) // [start = 6, end = 90) // Insert item at index 5 list.splice(5, 0, { title: "N0", group: 0 }) // [start = 5, end = 91) // Insert item at index 90 list.splice(90, 0, { title: "N0", group: 0 }) // [start = 4, end = 92) return Helper.ListView.waitForReady(listView, -1)().then(layoutTester.testLayout); }).then(function () { /* reload */ LiveUnit.LoggingCore.logComment("Test: reload"); list.reverse(); // trigger reload, make sure the range is the entire list return Helper.ListView.waitForReady(listView, -1)().then(layoutTester.testLayout); }).then(function () { /* remove at index 33 */ LiveUnit.LoggingCore.logComment("Test: single remove at index 33"); list.splice(33, 1); // [firstIndex = 32, lastIndex = 33) return Helper.ListView.waitForReady(listView, -1)().then(layoutTester.testLayout); }).then(function () { /* remove from the end */ LiveUnit.LoggingCore.logComment("Test: single remove from the end"); list.splice(list.length - 1, 1); // [firstIndex = list.length - 2, lastIndex = list.length - 2] return Helper.ListView.waitForReady(listView, -1)().then(layoutTester.testLayout); }).then(function () { /* remove at index 0 */ LiveUnit.LoggingCore.logComment("Test: single remove from index 0"); list.splice(0, 1); // [firstIndex = 0, lastIndex = 0] return Helper.ListView.waitForReady(listView, -1)().then(layoutTester.testLayout); }).then(function () { /* insert at 0 */ LiveUnit.LoggingCore.logComment("Test: single insert at index 0"); list.splice(0, 0, { title: "N0", group: 0 });// [firstIndex = 0, lastIndex = 1] return Helper.ListView.waitForReady(listView, -1)().then(layoutTester.testLayout); }).then(function () { /* insert at end */ LiveUnit.LoggingCore.logComment("Test: single insert at the end"); list.splice(list.length, 0, { title: "N0", group: 0 });// [firstIndex = list.length - 1, lastIndex = list.length] return Helper.ListView.waitForReady(listView, -1)().then(layoutTester.testLayout); }).then(function () { /* insert at index 44*/ LiveUnit.LoggingCore.logComment("Test: single insert at index 44"); list.splice(44, 0, { title: "N0", group: 0 });// [firstIndex = 43, lastIndex = 45] return Helper.ListView.waitForReady(listView, -1)().then(layoutTester.testLayout); }).then(function () { /* change at index 60 */ LiveUnit.LoggingCore.logComment("Test: change at index 60"); list.setAt(60, { title: "N0", group: 0 }); // [firstIndex = 59, lastIndex = 61] return Helper.ListView.waitForReady(listView, -1)().then(layoutTester.testLayout); }).then(function () { /* change at index 0 */ LiveUnit.LoggingCore.logComment("Test: change at index 0"); list.setAt(0, { title: "N0", group: 0 }); // [firstIndex = 0, lastIndex = 1] return Helper.ListView.waitForReady(listView, -1)().then(layoutTester.testLayout); }).then(function () { /* change last index */ LiveUnit.LoggingCore.logComment("Test: change at last index"); list.setAt(list.length - 1, { title: "N0", group: 0 }); // [firstIndex = list.length - 2, lastIndex = list.length-1] return Helper.ListView.waitForReady(listView, -1)().then(layoutTester.testLayout); }).then(function () { /* Move from first index to last index */ LiveUnit.LoggingCore.logComment("Test: move item from first index to last index"); list.move(0, list.length - 1)// [firstIndex = 0, lastIndex = list.length - 1] return Helper.ListView.waitForReady(listView, -1)().then(layoutTester.testLayout); }).then(function () { /* Move from last index to first index */ LiveUnit.LoggingCore.logComment("Test: move item from last index to first index"); list.move(list.length - 1, 0) // [firstIndex = 0, lastIndex = list.length - 1] return Helper.ListView.waitForReady(listView, -1)().then(layoutTester.testLayout); }).then(function () { placeholder.parentNode.removeChild(placeholder); complete(); }); }; // Verifies that when the ListView creates containers asynchronously, it only // lays out the new containers rather than also relaying out the existing containers. testChangedRangeDuringAsyncContainerCreation = function (complete) { WinJS.UI._VirtualizeContentsView._chunkSize = 10; WinJS.UI._VirtualizeContentsView._maxTimePerCreateContainers = 0; var lastLaidOutItem = 0; var placeholder = createListViewElement(); var data = initData(COUNT), list = new WinJS.Binding.List(data); var listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: generateRenderer("40px"), layout: { initialize: function (site, groups) { return "vertical"; }, layout: function (tree, range) { // Note that it's okay for previous changedRange.lastIndex === current changedRange.firstIndex so // that one can use changedRange to infer the set of group indices that need to be laid out. LiveUnit.Assert.isTrue(range.firstIndex >= lastLaidOutItem, "changedRange includes items that are already laid out"); lastLaidOutItem = range.lastIndex; if (lastLaidOutItem + 1 === COUNT) { placeholder.parentNode.removeChild(placeholder); complete(); } }, } }); }; // Verifies that when the ListView has no containers, layout is called with // a changedRange of { firstIndex: 0, lastIndex: -1 }. testChangedRangeForDeleteAll = function (complete) { var placeholder = createListViewElement(); var data = initData(COUNT), list = new WinJS.Binding.List(data), deletedAll = false, verifiedChangedRange = false; var listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: generateRenderer("40px"), layout: { initialize: function (site, groups) { return "vertical"; }, layout: function (tree, range) { if (deletedAll) { LiveUnit.Assert.areEqual(0, range.firstIndex); LiveUnit.Assert.areEqual(-1, range.lastIndex); verifiedChangedRange = true; } }, } }); Helper.ListView.waitForReady(listView)().then(function () { list.splice(0, COUNT); deletedAll = true; return Helper.ListView.waitForReady(listView, -1)(); }).done(function () { LiveUnit.Assert.isTrue(verifiedChangedRange, "Layout should have been called after deleting all of the items"); complete(); }); }; testBackdropClass = function (complete) { if (!Helper.Browser.supportsCSSGrid) { complete(); return; } WinJS.UI._VirtualizeContentsView._disableCustomPagesPrefetch = true; var placeholder = createListViewElement(); var data = initData(COUNT), list = new WinJS.Binding.List(data); var listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: generateRenderer("100px"), layout: new WinJS.UI.CellSpanningLayout({ groupInfo: { enableCellSpanning: true, cellWidth: 100, cellHeight: 100 }, itemInfo: function () { return { width: 100, height: 100 } }, }) }); function verifyBackdrop(firstVisible, realized) { LiveUnit.Assert.areEqual(realized, placeholder.querySelectorAll(".win-item").length); LiveUnit.Assert.areEqual(list.length, placeholder.querySelectorAll(".win-container").length); LiveUnit.Assert.areEqual(list.length - realized, placeholder.querySelectorAll(".win-backdrop").length); var containers = placeholder.querySelectorAll("win-container"); for (var i = 0; i < containers.length; i++) { var isRealized = i >= firstVisible && i < (firstVisible + realized); LiveUnit.Assert.areEqual(isRealized, !WinJS.Utilities.hasClass(containers[i], "win-backdrop")); } } return Helper.ListView.waitForReady(listView)().then(function () { verifyBackdrop(0, 9 * 3); listView.indexOfFirstVisible = 27; return Helper.ListView.waitForDeferredAction(listView)(); }).then(function () { verifyBackdrop(0, 9 * 5); listView.indexOfFirstVisible = 0; return Helper.ListView.waitForDeferredAction(listView)(); }).then(function () { verifyBackdrop(0, 9 * 3); list.unshift({ title: "NewItem0", group: 0 }); return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { verifyBackdrop(0, 9 * 3); list.shift(); return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { // We just deleted an item, and a new one won't be realized because of the // skip realization on deletes optimization. // verifyBackdrop(0, (9 * 3) - 1); for (var i = 0; i < 20; i++) { list.push({ title: "NewItem0", group: 0 }); } return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { verifyBackdrop(0, 9 * 3); placeholder.parentNode.removeChild(placeholder); complete(); }); }; testDeferRealizationOnDelete = function (complete) { WinJS.UI._VirtualizeContentsView._disableCustomPagesPrefetch = true; var element = document.createElement("div"); element.style.width = "300px"; element.style.height = "300px"; VirtualizeContentsViewTestHost.appendChild(element); var list = createBindingList(100); var listView = new ListView(element, { layout: new WinJS.UI.GridLayout(), itemDataSource: list.dataSource, itemTemplate: fixedSizeTemplate }); Helper.ListView.waitForReady(listView)().done(function () { //Deleting one item won't cause a full realize LiveUnit.Assert.areEqual(45, getNumberOfItemsRealized()); list.splice(0, 1); Helper.ListView.waitForReady(listView, 100)().then(function () { LiveUnit.Assert.areEqual(99, list.length); LiveUnit.Assert.areEqual(1, listView._view.deletesWithoutRealize); LiveUnit.Assert.areEqual(44, getNumberOfItemsRealized()); // Deleting 12 more items still won't cause a full realize list.splice(0, 12); Helper.ListView.waitForReady(listView, 100)().then(function () { LiveUnit.Assert.areEqual(87, list.length); LiveUnit.Assert.areEqual(13, listView._view.deletesWithoutRealize); LiveUnit.Assert.areEqual(32, getNumberOfItemsRealized()); // Deleting one more item will cause a full realize because we already lost one viewport full // of data list.splice(0, 1); Helper.ListView.waitForReady(listView, 100)().then(function () { LiveUnit.Assert.areEqual(86, list.length); LiveUnit.Assert.areEqual(0, listView._view.deletesWithoutRealize); LiveUnit.Assert.areEqual(45, getNumberOfItemsRealized()); element.parentNode.removeChild(element); complete(); }); }); }); }); }; testDeferRealizationOnDeleteNotJustDeletes = function (complete) { WinJS.UI._VirtualizeContentsView._disableCustomPagesPrefetch = true; var element = document.createElement("div"); element.style.width = "300px"; element.style.height = "300px"; VirtualizeContentsViewTestHost.appendChild(element); var list = createBindingList(100); var listView = new ListView(element, { layout: new WinJS.UI.GridLayout(), itemDataSource: list.dataSource, itemTemplate: fixedSizeTemplate }); Helper.ListView.waitForReady(listView)().then(function () { LiveUnit.Assert.areEqual(45, getNumberOfItemsRealized()); //Deleting one item + inserting another one will cause a full realize list.splice(0, 1); list.splice(0, 0, { title: "title New", itemWidth: "80px", itemHeight: "80px" }); Helper.ListView.waitForReady(listView, 100)().then(function () { LiveUnit.Assert.areEqual(100, list.length); LiveUnit.Assert.areEqual(0, listView._view.deletesWithoutRealize); LiveUnit.Assert.areEqual(45, getNumberOfItemsRealized()); element.parentNode.removeChild(element); complete(); }); }); }; testDeferRealizationOnDeleteWithDataModificationsDuringAnimation = function (complete) { var listViewNode = document.createElement("DIV"); listViewNode.innerHTML = "
"; VirtualizeContentsViewTestHost.appendChild(listViewNode); var renderer = function (itemPromise) { return itemPromise.then(function (item) { var element = document.createElement("DIV"); element.textContent = item.data.title; element.style.height = "40px"; element.style.width = "400px"; return element; }); }; var getDataSource = function () { var items = []; for (var i = 0; i < 100; ++i) { items[i] = { title: "Tile" + i }; } return new WinJS.Binding.List(items).dataSource; } var listLayout = new WinJS.UI.ListLayout(); var listView = new ListView(listViewNode.firstElementChild, { itemDataSource: getDataSource(), itemTemplate: renderer, layout: listLayout }); Helper.ListView.waitForReady(listView, -1)().then(function () { listView.itemDataSource.list.splice(0, 2); setTimeout(function () { listView.itemDataSource.list.splice(0, 1); requestAnimationFrame(function () { for (var i = 0; i < 3; i++) { listView.itemDataSource.insertAtStart(null, { title: "New Title: " + i }); } Helper.ListView.waitForReady(listView, -1)().then(function () { LiveUnit.Assert.areEqual("New Title: 2", listView.elementFromIndex(0).textContent); LiveUnit.Assert.areEqual("New Title: 1", listView.elementFromIndex(1).textContent); LiveUnit.Assert.areEqual("New Title: 0", listView.elementFromIndex(2).textContent); LiveUnit.Assert.areEqual("Tile3", listView.elementFromIndex(3).textContent); LiveUnit.Assert.areEqual("Tile4", listView.elementFromIndex(4).textContent); VirtualizeContentsViewTestHost.removeChild(listViewNode); complete(); }); }); }, 300); }); }; // Regression test for WinBlue: 88018 testEnsureVisibleAfterDataChange = function (complete) { WinJS.UI._VirtualizeContentsView._disableCustomPagesPrefetch = true; var newNode = document.createElement("div"); newNode.innerHTML = "
" + ""; VirtualizeContentsViewTestHost.appendChild(newNode); var listView = setupMailStyleListView(); var tests = [ function () { // First, delete a single item (this will use the deferRealizationOnDelete optimization). // Then, invoke ensurevisible(0), which does not cause a full relayout // This should complete the animation, layout items properly and not leave any gaps // in the ListView. // listView.itemDataSource.itemFromIndex(0).then(function (item) { listView.itemDataSource.beginEdits(); listView.itemDataSource.remove(item.key); listView.itemDataSource.endEdits(); listView.ensureVisible(0); }); return true; }, function () { var initialContainersCount = 9; // Verify that we did not do a full realize // LiveUnit.Assert.areEqual(initialContainersCount - 1, getNumberOfItemsRealized(), "With deferRealizationOnDelete on, the ListView should have 1 less container since the scenario should not do a full realize"); VirtualizeContentsViewTestHost.removeChild(newNode); complete(); } ]; Helper.ListView.runTests(listView, tests); }; testScrollAfterSkippedRealization = function (complete) { Helper.initUnhandledErrors(); var newNode = document.createElement("div"); newNode.innerHTML = "
" + ""; VirtualizeContentsViewTestHost.appendChild(newNode); var listView = setupMailStyleListView(); var key; listView.itemDataSource.itemFromIndex(0).then(function (item) { key = item.key; return Helper.ListView.waitForReady(listView)(); }).then(function () { listView.scrollPosition = 1850; listView.itemDataSource.remove(key); return WinJS.Promise.timeout(10); }).then(function () { listView.scrollPosition += 10; return Helper.validateUnhandledErrorsOnIdle(); }).done(function () { VirtualizeContentsViewTestHost.removeChild(newNode); complete(); }); }; testSingleDeleteUpdateFollowedByInserts = function (complete) { WinJS.UI._VirtualizeContentsView._disableCustomPagesPrefetch = true; var newNode = document.createElement("div"); newNode.innerHTML = "
" + ""; VirtualizeContentsViewTestHost.appendChild(newNode); var listView = setupMailStyleListView(); Helper.ListView.waitForReady(listView)().then(function () { // Delete a single item. This will use the defer realization on delete optimization and // try to skip a full realization. However, another update will arrive to insert // two items. This should force us to do a full realization. // listView.itemDataSource.itemFromIndex(0).then(function (item) { listView.itemDataSource.beginEdits(); listView.itemDataSource.remove(item.key); listView.itemDataSource.endEdits(); listView.itemDataSource.beginEdits(); var data1 = { title: "Inserted Item 1" }; var data2 = { title: "Inserted Item 2" }; listView.itemDataSource.insertAtStart(null, data1); listView.itemDataSource.insertAtStart(null, data2); listView.itemDataSource.endEdits(); }); return Helper.ListView.waitForDeferredAction(listView)(); }).then(function () { var initialContainersCount = 9; // Verify that we did a full realize // LiveUnit.Assert.areEqual(initialContainersCount, getNumberOfItemsRealized(), "The ListView should have all the containers it started with since the scenario should do a full realize"); complete(); }); }; // Regression test for WinBlue: 88791 // testCompleteEventOnReloadForHightZeroFollowedByResize = function (complete) { var newNode = document.createElement("div"); newNode.innerHTML = "
" + ""; VirtualizeContentsViewTestHost.appendChild(newNode); var renderer = function (itemPromise) { return itemPromise.then(function (item) { var element = document.createElement("DIV"); element.textContent = item.data.title; element.style.height = "40px"; element.style.width = "400px"; return element; }); }; var items = []; for (var i = 0; i < 100; ++i) { items[i] = { title: "Tile" + i }; } var controller = { directivesForMethod: function (method, args) { return { callMethodSynchronously: false, sendChangeNotifications: true, countBeforeDelta: 0, countAfterDelta: 0, countBeforeOverride: -1, countAfterOverride: -1, delay: 50 }; } }; var dataSource = Helper.ItemsManager.createTestDataSource(items.slice(0), controller, null); var layout = new WinJS.UI.ListLayout(); var listView = new ListView(newNode.firstElementChild, { itemDataSource: dataSource, selectionMode: "multi", itemTemplate: Helper.ListView.createRenderer("simpleTemplate"), layout: layout }); listView.addEventListener("loadingstatechanged", loadingStateChanged); WinJS.Promise.timeout(10).then(function () { listView.itemDataSource.beginEdits(); var data1 = { title: "Inserted Item 1" }; listView.itemDataSource.insertAtStart(null, data1); listView.itemDataSource.endEdits(); WinJS.Promise.timeout(50).then(function () { listView.element.style.height = "400px"; }); }); function loadingStateChanged() { if (listView.loadingState === "complete") { listView.removeEventListener("loadingstatechanged", loadingStateChanged); // Ensure that when we start with height 0 and get a resize, we realize items // for the last viewport value and fire the complete event. // VirtualizeContentsViewTestHost.removeChild(newNode); complete(); } } }; testReadySignalOrder = function (complete) { WinJS.UI._VirtualizeContentsView._disableCustomPagesPrefetch = true; var element = document.createElement("div"); element.style.width = "100px"; element.style.height = "200px"; VirtualizeContentsViewTestHost.appendChild(element); var rendering = []; var renderer = function (itemPromise) { var itemIndex; var element = document.createElement("DIV"); element.style.width = element.style.height = "100px"; return { element: element, renderComplete: itemPromise.then(function (item) { itemIndex = item.index; rendering.push(itemIndex + "e"); element.textContent = item.data.title; return item.ready; }).then(function () { rendering.push(itemIndex + "r"); }) }; }; var list = createBindingList(100); var listView = new ListView(element, { layout: new WinJS.UI.GridLayout(), itemDataSource: list.dataSource, itemTemplate: renderer }); Helper.ListView.waitForReady(listView)().then(function () { /* during startup order is - render on screen items - raise ready signal for on screen items - render off screen items - raise ready signal for off screen items The item at index 0 is handled differently since it is rendered and its ready signal is raised by layout which measures it. */ Helper.ListView.elementsEqual(["0e", "0r", "1e", "1r", "2e", "3e", "4e", "5e", "2r", "3r", "4r", "5r"], rendering); rendering = []; // scroll to right listView.indexOfFirstVisible = 16; return Helper.ListView.waitForDeferredAction(listView)(); }).then(function () { /* during scrolling order is: - render on screen items - render front off screen items - raise ready signal for on screen items - raise ready signal for front off screen items - render back off screen items - raise ready signal for back off screen items */ Helper.ListView.elementsEqual(["16e", "17e", "18e", "19e", "20e", "21e", "16r", "17r", "18r", "19r", "20r", "21r", "15e", "14e", "13e", "12e", "15r", "14r", "13r", "12r"], rendering); rendering = []; // scroll to left listView.indexOfFirstVisible = 6; return Helper.ListView.waitForDeferredAction(listView)(); }).then(function () { Helper.ListView.elementsEqual(["7e", "6e", "5e", "4e", "3e", "2e", "7r", "6r", "5r", "4r", "3r", "2r", "8e", "9e", "10e", "11e", "8r", "9r", "10r", "11r"], rendering); element.parentNode.removeChild(element); complete(); }); }; testReadySignalOrderWithSlowDataSource = function (complete) { function createDataSource() { var blockSize = 10, dataBlocks = [], check = []; for (var i = 0; i < 50; i++) { dataBlocks.push(new WinJS._Signal()); check.push(false); } function unblockRange(first, last) { for (var i = Math.floor(first / blockSize); i <= Math.floor(last / blockSize); i++) { dataBlocks[i].complete(); check[i] = true; } } unblockRange(0, 10); unblockRange(300, 499); unblockRange(210, 239); function pause() { return WinJS.Promise.timeout(200); } pause().then(function () { unblockRange(240, 259); return pause(); }).then(function () { unblockRange(200, 209); return pause(); }).then(function () { unblockRange(280, 299); return pause(); }).then(function () { unblockRange(260, 279); return pause(); }).then(function () { unblockRange(0, 50); return pause(); }).then(function () { unblockRange(150, 199); return pause(); }).then(function () { unblockRange(50, 79); return pause(); }).then(function () { unblockRange(80, 109); return pause(); }).then(function () { unblockRange(110, 129); return pause(); }).then(function () { unblockRange(130, 149); return pause(); }); var dataSource = { itemsFromIndex: function (index, countBefore, countAfter) { return new WinJS.Promise(function (complete, error) { if (index >= 0 && index < (blockSize * dataBlocks.length)) { var startIndex = Math.max(0, index - countBefore), endIndex = Math.min(index + countAfter, Math.min(startIndex + blockSize - 1, (blockSize * dataBlocks.length) - 1)), size = endIndex - startIndex + 1; var startBlock = Math.floor(startIndex / blockSize), endBlock = Math.floor(endIndex / blockSize); WinJS.Promise.join([dataBlocks[startBlock].promise, dataBlocks[endBlock].promise]).then(function () { var items = []; for (var i = startIndex; i < startIndex + size; i++) { items.push({ key: i.toString(), data: { title: "Tile" + i } }); } complete({ items: items, offset: index - startIndex, totalCount: (blockSize * dataBlocks.length), absoluteIndex: index }); }); } else { complete({}); } }); }, getCount: function () { return WinJS.Promise.wrap((blockSize * dataBlocks.length)); } }; return new WinJS.UI.ListDataSource(dataSource); } var element = document.createElement("div"); element.style.width = "500px"; element.style.height = "500px"; VirtualizeContentsViewTestHost.appendChild(element); var readyIndices = []; var renderer = function (itemPromise) { var itemIndex; var element = document.createElement("DIV"); element.style.width = element.style.height = "50px"; return { element: element, renderComplete: itemPromise.then(function (item) { itemIndex = item.index; element.textContent = item.data.title; return item.ready; }).then(function () { readyIndices.push(itemIndex); }) }; }; var listView = new ListView(element, { layout: new WinJS.UI.GridLayout(), itemDataSource: createDataSource(), itemTemplate: renderer, indexOfFirstVisible: 200 }); Helper.ListView.waitForReady(listView)().then(function () { var expected = []; for (var i = 200; i < 500; i++) { expected.push(i); } for (i = 199; i >= 0; i--) { expected.push(i); } Helper.ListView.elementsEqual(expected, readyIndices); element.parentNode.removeChild(element); complete(); }); }; testSuppressCallbacksDuringScrolling = function (complete) { function checkTile(listView, index) { var element = listView.elementFromIndex(index); LiveUnit.Assert.areEqual("Tile" + index, element.textContent); } var element = document.createElement("div"); element.style.width = "300px"; element.style.height = "300px"; VirtualizeContentsViewTestHost.appendChild(element); var stateChangeCounter = { itemsLoading: 0, viewPortLoaded: 0, itemsLoaded: 0, complete: 0 }; element.addEventListener("loadingstatechanged", function (eventObject) { stateChangeCounter[eventObject.target['winControl'].loadingState]++; }); var renderer = function (itemPromise) { var itemIndex; var element = document.createElement("DIV"); element.style.width = element.style.height = "100px"; return { element: element, renderComplete: itemPromise.then(function (item) { itemIndex = item.index; element.textContent = item.data.title; }) }; }; var data = initData(200), list = new WinJS.Binding.List(data); var listView = new ListView(element, { layout: new WinJS.UI.GridLayout(), itemDataSource: list.dataSource, itemTemplate: renderer }); Helper.ListView.waitForReady(listView)().then(function () { LiveUnit.Assert.areEqual(1, stateChangeCounter.itemsLoading); LiveUnit.Assert.areEqual(1, stateChangeCounter.viewPortLoaded); LiveUnit.Assert.areEqual(1, stateChangeCounter.itemsLoaded); LiveUnit.Assert.areEqual(1, stateChangeCounter.complete); listView._onMSManipulationStateChanged({ currentState: 1 }); WinJS.Utilities.setScrollPosition(listView._viewport, { scrollLeft: 1500, scrollTop: 0 }); return WinJS.Promise.timeout(300); }).then(function () { LiveUnit.Assert.areEqual(45, listView.indexOfFirstVisible); checkTile(listView, 45); LiveUnit.Assert.areEqual(2, stateChangeCounter.itemsLoading); LiveUnit.Assert.areEqual(1, stateChangeCounter.viewPortLoaded); LiveUnit.Assert.areEqual(1, stateChangeCounter.itemsLoaded); LiveUnit.Assert.areEqual(1, stateChangeCounter.complete); WinJS.Utilities.setScrollPosition(listView._viewport, { scrollLeft: 3000, scrollTop: 0 }); return WinJS.Promise.timeout(300); }).then(function () { LiveUnit.Assert.areEqual(90, listView.indexOfFirstVisible); checkTile(listView, 90); LiveUnit.Assert.areEqual(2, stateChangeCounter.itemsLoading); LiveUnit.Assert.areEqual(1, stateChangeCounter.viewPortLoaded); LiveUnit.Assert.areEqual(1, stateChangeCounter.itemsLoaded); LiveUnit.Assert.areEqual(1, stateChangeCounter.complete); listView._onMSManipulationStateChanged({ currentState: 0 }); return WinJS.Promise.timeout(100); }).then(function () { LiveUnit.Assert.areEqual(2, stateChangeCounter.itemsLoading); LiveUnit.Assert.areEqual(2, stateChangeCounter.viewPortLoaded); LiveUnit.Assert.areEqual(2, stateChangeCounter.itemsLoaded); LiveUnit.Assert.areEqual(2, stateChangeCounter.complete); WinJS.Utilities.setScrollPosition(listView._viewport, { scrollLeft: 0, scrollTop: 0 }); return WinJS.Promise.timeout(50); }).then(function () { LiveUnit.Assert.areEqual(3, stateChangeCounter.itemsLoading); LiveUnit.Assert.areEqual(2, stateChangeCounter.viewPortLoaded); LiveUnit.Assert.areEqual(2, stateChangeCounter.itemsLoaded); LiveUnit.Assert.areEqual(2, stateChangeCounter.complete); return Helper.ListView.waitForReady(listView)(); }).then(function () { LiveUnit.Assert.areEqual(3, stateChangeCounter.itemsLoading); LiveUnit.Assert.areEqual(3, stateChangeCounter.viewPortLoaded); LiveUnit.Assert.areEqual(3, stateChangeCounter.itemsLoaded); LiveUnit.Assert.areEqual(3, stateChangeCounter.complete); element.parentNode.removeChild(element); complete(); }); }; testWaitingForItemRenderers = function (complete) { var element = document.createElement("div"); element.style.width = "300px"; element.style.height = "300px"; VirtualizeContentsViewTestHost.appendChild(element); var signal = new WinJS._Signal(), signaled; var renderer = function (itemPromise) { var itemIndex; var element = document.createElement("DIV"); element.style.width = element.style.height = "100px"; return { element: element, renderComplete: itemPromise.then(function (item) { itemIndex = item.index; element.textContent = item.data.title; return signal.promise; }) }; }; var data = initData(200), list = new WinJS.Binding.List(data); var listView = new ListView(element, { layout: new WinJS.UI.GridLayout(), itemDataSource: list.dataSource, itemTemplate: renderer }); var stateChangeCounter = { itemsLoading: 0, viewPortLoaded: 0, itemsLoaded: 0, complete: 0 }; element.addEventListener("loadingstatechanged", function () { stateChangeCounter[listView.loadingState]++; switch (listView.loadingState) { case "viewPortLoaded": LiveUnit.Assert.areEqual(1, stateChangeCounter.viewPortLoaded); LiveUnit.Assert.areEqual(0, stateChangeCounter.itemsLoaded); LiveUnit.Assert.areEqual(0, stateChangeCounter.complete); break; case "itemsLoaded": LiveUnit.Assert.areEqual(1, stateChangeCounter.viewPortLoaded); LiveUnit.Assert.areEqual(1, stateChangeCounter.itemsLoaded); LiveUnit.Assert.areEqual(0, stateChangeCounter.complete); WinJS.Promise.timeout(300).then(function () { LiveUnit.Assert.areEqual(0, stateChangeCounter.complete); signaled = true; signal.complete(); }); break; case "complete": LiveUnit.Assert.isTrue(signaled); LiveUnit.Assert.areEqual(1, stateChangeCounter.viewPortLoaded); LiveUnit.Assert.areEqual(1, stateChangeCounter.itemsLoaded); LiveUnit.Assert.areEqual(1, stateChangeCounter.complete); element.parentNode.removeChild(element); complete(); break; } }); }; testAsynchronousLayoutsDoNotRunConcurrently = function (complete) { WinJS.UI._VirtualizeContentsView._maxTimePerCreateContainers = 0; var placeholder = createListViewElement(); var stateChangeCounter = { itemsLoading: 0, viewPortLoaded: 0, itemsLoaded: 0, complete: 0 }; placeholder.addEventListener("loadingstatechanged", function (eventObject) { stateChangeCounter[eventObject.target['winControl'].loadingState]++; }); var data = initData(BIG_DATASET), list = new WinJS.Binding.List(data); var layouInProgress; var listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: generateRenderer("100px"), layout: { initialize: function (site, groups) { return "vertical"; }, layout: function (tree, range) { LiveUnit.Assert.isFalse(layouInProgress); layouInProgress = true; // this is clearly broken var completeLayoutPromise = new WinJS.Promise(function (c) { }); completeLayoutPromise.then( function (c: any) { WinJS.Promise.timeout(5 * 1000).then(function () { layouInProgress = false; c(); }); }, function (error) { layouInProgress = false; } ); return { realizedRangeComplete: WinJS.Promise.wrap(), layoutComplete: completeLayoutPromise }; }, itemsFromRange: function (left, right) { return { firstIndex: 0, lastIndex: 3 }; } } }); var jobNode; listView._view._scheduleLazyTreeCreation = function () { jobNode = Object.getPrototypeOf(listView._view)._scheduleLazyTreeCreation.call(listView._view); jobNode.pause(); return jobNode; }; return Helper.ListView.waitForReady(listView)().then(function () { LiveUnit.Assert.areEqual(1, stateChangeCounter.viewPortLoaded); LiveUnit.Assert.areEqual(1, stateChangeCounter.itemsLoaded); LiveUnit.Assert.areEqual(1, stateChangeCounter.complete); LiveUnit.Assert.isTrue(placeholder.querySelectorAll(".win-container").length < BIG_DATASET); listView._view._state.layoutNewContainers(); jobNode.resume(); return listView._view._creatingContainersWork ? listView._view._creatingContainersWork.promise : null; }).then(function () { LiveUnit.Assert.areEqual(1, stateChangeCounter.viewPortLoaded); LiveUnit.Assert.areEqual(1, stateChangeCounter.itemsLoaded); LiveUnit.Assert.areEqual(1, stateChangeCounter.complete); LiveUnit.Assert.areEqual(BIG_DATASET, placeholder.querySelectorAll(".win-container").length); placeholder.parentNode.removeChild(placeholder); complete(); }); }; testEditDuringAsyncLayout = function (complete) { var placeholder = createListViewElement(); var data = initData(), list = new WinJS.Binding.List(data); var layouInProgress, layuoutCanceled; var listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: generateRenderer("100px"), layout: { initialize: function (site, groups) { return "vertical"; }, layout: function (tree, range) { LiveUnit.Assert.isFalse(layouInProgress); layouInProgress = true; // this is clearly broken var completeLayoutPromise = new WinJS.Promise(function (c) { }); completeLayoutPromise.then( function (c: any) { WinJS.Promise.timeout(10 * 1000).then(function () { layouInProgress = false; c(); }); }, function (error) { layuoutCanceled = true; layouInProgress = false; } ); return { realizedRangeComplete: WinJS.Promise.wrap(), layoutComplete: completeLayoutPromise }; }, itemsFromRange: function (left, right) { return { firstIndex: 0, lastIndex: 3 }; } } }); return Helper.ListView.waitForReady(listView)().then(function () { LiveUnit.Assert.isTrue(layouInProgress); list.unshift({ title: "SNI", group: 0 }); return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { LiveUnit.Assert.isTrue(layuoutCanceled); layuoutCanceled = false; validateFlatTree(listView); listView.dispose(); LiveUnit.Assert.isTrue(layuoutCanceled); LiveUnit.Assert.isFalse(layouInProgress); placeholder.parentNode.removeChild(placeholder); complete(); }); }; testDatasourceChangeDuringAsyncLayout = function (complete) { var placeholder = createListViewElement(); var data1 = initData(), list1 = new WinJS.Binding.List(data1), data2 = initData(1), list2 = new WinJS.Binding.List(data2); var layouInProgress, layuoutCanceled; var listView = new ListView(placeholder, { itemDataSource: list1.dataSource, itemTemplate: generateRenderer("100px"), layout: { initialize: function (site, groups) { return "vertical"; }, layout: function (tree, range) { LiveUnit.Assert.isFalse(layouInProgress); layouInProgress = true; // this is clearly broken var completeLayoutPromise = new WinJS.Promise(function (c) { }); completeLayoutPromise.then( function (c: any) { WinJS.Promise.timeout(10 * 1000).then(function () { layouInProgress = false; c(); }); }, function (error) { layuoutCanceled = true; layouInProgress = false; } ); return { realizedRangeComplete: WinJS.Promise.wrap(), layoutComplete: completeLayoutPromise }; }, itemsFromRange: function (left, right) { return { firstIndex: 0, lastIndex: 3 }; } } }); return Helper.ListView.waitForReady(listView)().then(function () { LiveUnit.Assert.isTrue(layouInProgress); listView.itemDataSource = list2.dataSource; return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { LiveUnit.Assert.isTrue(layuoutCanceled); layuoutCanceled = false; validateFlatTree(listView); listView.dispose(); LiveUnit.Assert.isTrue(layuoutCanceled); LiveUnit.Assert.isFalse(layouInProgress); placeholder.parentNode.removeChild(placeholder); complete(); }); }; testMaxDeferredItemCleanup = function (complete) { var MAX_DEFERRED_ITEM_CLEANUP = 10; var itemsCount = BIG_DATASET, list = new WinJS.Binding.List(initData(itemsCount)); var placeholder = createListViewElement(); var realizedItemsCount = 0; function renderer(itemPromise) { return itemPromise.then(function (item) { var element = document.createElement("div"); element.className = "myVVTestClass win-disposable"; element.textContent = item.data.title; element.style.width = element.style.height = "100px"; element.dispose = function () { if (this.disposed) { LiveUnit.Assert.fail("Disposed was called again."); } this.disposed = true; realizedItemsCount--; }; realizedItemsCount++; return element; }); } var listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: renderer, layout: new WinJS.UI.GridLayout() }); LiveUnit.Assert.areEqual(Number.MAX_VALUE, listView.maxDeferredItemCleanup); listView.maxDeferredItemCleanup = 11; LiveUnit.Assert.areEqual(11, listView.maxDeferredItemCleanup); listView.maxDeferredItemCleanup = undefined; LiveUnit.Assert.areEqual(0, listView.maxDeferredItemCleanup); listView.maxDeferredItemCleanup = 12; LiveUnit.Assert.areEqual(12, listView.maxDeferredItemCleanup); listView.maxDeferredItemCleanup = null; LiveUnit.Assert.areEqual(0, listView.maxDeferredItemCleanup); listView.maxDeferredItemCleanup = -123; LiveUnit.Assert.areEqual(0, listView.maxDeferredItemCleanup); listView.maxDeferredItemCleanup = 0; LiveUnit.Assert.areEqual(0, listView.maxDeferredItemCleanup); listView.maxDeferredItemCleanup = MAX_DEFERRED_ITEM_CLEANUP; function scroll() { return new WinJS.Promise(function (complete) { function scrollWorker() { LiveUnit.Assert.isTrue(realizedItemsCount <= MAX_DEFERRED_ITEM_CLEANUP + (listView._view.end - listView._view.begin)); if (listView.scrollPosition < 2500) { listView.scrollPosition = listView.scrollPosition + 50; setTimeout(scrollWorker, 100); } else { complete(); } } scrollWorker(); }); } return Helper.ListView.waitForReady(listView)().then(function () { listView._onMSManipulationStateChanged({ currentState: 1 }); return scroll(); }).then(function () { placeholder.parentNode.removeChild(placeholder); complete(); }); }; testInsertsAnimationStartsBeforeRealizationIsDone = function (complete) { WinJS.UI._VirtualizeContentsView._disableCustomPagesPrefetch = true; var element = document.createElement("div"); element.style.width = "300px"; element.style.height = "300px"; VirtualizeContentsViewTestHost.appendChild(element); function getNumberOfNewItemsRealized() { return document.querySelectorAll(".win-listview .win-item.new").length; } var list = createBindingList(100); var listView = new ListView(element, { layout: new WinJS.UI.ListLayout(), itemDataSource: list.dataSource, itemTemplate: function (itemPromise) { return itemPromise.then(function (item) { var el = document.createElement("div"); el.textContent = item.data.title; el.style.width = "417px"; el.style.height = "20px"; el.className = item.data.className ? item.data.className : "" return el; }); } }); Helper.ListView.waitForReady(listView)().then(function () { LiveUnit.Assert.areEqual(45, getNumberOfItemsRealized()); var oldExecuteAnimations = listView.layout.executeAnimations; listView.layout.executeAnimations = function () { LiveUnit.Assert.isTrue(getNumberOfNewItemsRealized() < 10, "By the time the animation starts, we should have not realized all the items inserted"); oldExecuteAnimations.call(listView.layout); }; for (var i = 0; i < 30; i++) { list.splice(12 + i, 0, { title: "title New " + i, className: "new" }); } Helper.ListView.waitForDeferredAction(listView)().then(function () { LiveUnit.Assert.areEqual(30, getNumberOfNewItemsRealized(), "All 30 inserted items should now be realized"); LiveUnit.Assert.areEqual(45, getNumberOfItemsRealized()); VirtualizeContentsViewTestHost.removeChild(element); complete(); }); }); }; testDeleteAnimationStartsBeforeUpdateTreeIsDone = function (complete) { WinJS.UI._VirtualizeContentsView._disableCustomPagesPrefetch = true; var element = document.createElement("div"); element.style.width = "300px"; element.style.height = "300px"; VirtualizeContentsViewTestHost.appendChild(element); function getNumberOfNewItemsRealized() { return document.querySelectorAll(".win-listview .win-itemscontainer .win-item").length; } function getInnerHTMLForContainer(index) { return (document.querySelectorAll(".win-listview .win-itemscontainer .win-container")[index]).innerHTML; } var list = createBindingList(100); var listView = new ListView(element, { layout: new WinJS.UI.ListLayout(), itemDataSource: list.dataSource, itemTemplate: function (itemPromise) { return itemPromise.then(function (item) { var el = document.createElement("div"); el.textContent = item.data.title; el.style.height = "20px"; el.style.width = "417px"; el.className = item.data.className ? item.data.className : "" return el; }); } }); if (!listView.layout['_usingStructuralNodes']) { VirtualizeContentsViewTestHost.removeChild(element); complete(); return; } listView.ensureVisible(20); Helper.ListView.waitForReady(listView)().then(function () { LiveUnit.Assert.areEqual(51, getNumberOfItemsRealized()); var oldExecuteAnimations = listView.layout.executeAnimations; listView.layout.executeAnimations = function () { // Container at index 5 is above the viewport, so we did not reparent (it is deferred) LiveUnit.Assert.isTrue(getInnerHTMLForContainer(5).indexOf("title5") >= 0); // Container at index 6 is the first item in the viewport, so we did reparent it LiveUnit.Assert.isTrue(getInnerHTMLForContainer(6).indexOf("title7") >= 0); // Container at index 20 is the last item in the viewport, so we did reparent it LiveUnit.Assert.isTrue(getInnerHTMLForContainer(20).indexOf("title21") >= 0); //We should have an empty container since we deferred reparenting to start the animation sooner LiveUnit.Assert.areEqual("", getInnerHTMLForContainer(21)); // Container at index 22 is below the viewport, so we did not reparent (it is deferred) LiveUnit.Assert.isTrue(getInnerHTMLForContainer(22).indexOf("title22") >= 0); oldExecuteAnimations.call(listView.layout); }; list.splice(0, 1); Helper.ListView.waitForDeferredAction(listView)().then(function () { // Reparenting should be all done for items before, on, after the viewport LiveUnit.Assert.isTrue(getInnerHTMLForContainer(5).indexOf("title6") >= 0); LiveUnit.Assert.isTrue(getInnerHTMLForContainer(6).indexOf("title7") >= 0); LiveUnit.Assert.isTrue(getInnerHTMLForContainer(20).indexOf("title21") >= 0); LiveUnit.Assert.isTrue(getInnerHTMLForContainer(21).indexOf("title22") >= 0); LiveUnit.Assert.isTrue(getInnerHTMLForContainer(22).indexOf("title23") >= 0); LiveUnit.Assert.areEqual(50, getNumberOfItemsRealized(), "Defer realize optimization did not work"); VirtualizeContentsViewTestHost.removeChild(element); complete(); }); }); }; testInsertItemWithDeferredUnrealizedNonAnimatedItems = function (complete) { WinJS.UI._VirtualizeContentsView._disableCustomPagesPrefetch = true; var element = document.createElement("div"); element.style.width = "300px"; element.style.height = "300px"; VirtualizeContentsViewTestHost.appendChild(element); function getNumberOfNewItemsRealized() { return document.querySelectorAll(".win-listview .win-itemscontainer .win-item").length; } var list = createBindingList(100); var listView = new ListView(element, { layout: new WinJS.UI.ListLayout(), itemDataSource: list.dataSource, itemTemplate: function (itemPromise) { return itemPromise.then(function (item) { var el = document.createElement("div"); el.textContent = item.data.title; el.style.height = "20px"; el.style.width = "417px"; el.className = item.data.className ? item.data.className : "" return el; }); } }); listView.ensureVisible(20); Helper.ListView.waitForReady(listView)().then(function () { // We start with 51 realized items LiveUnit.Assert.areEqual(51, Object.keys(listView._view.items._itemData).length); LiveUnit.Assert.areEqual(51, getNumberOfItemsRealized()); var oldExecuteAnimations = listView.layout.executeAnimations; listView.layout.executeAnimations = function () { // We used to have 51 realized items, then inserted an item, which means that we will drop // one item. However, we deferred the unrealize of the 1 item that goes away. Before // the animation starts we should have 52 realized items. LiveUnit.Assert.areEqual(52, Object.keys(listView._view.items._itemData).length); // Ensure 2 are not sharing the same parent. Note that structural nodes being disabled // will cause the DOM to be slightly different. Without them we'll have 52 win-items // and with them we'll have only 51. LiveUnit.Assert.areEqual(0, document.querySelectorAll(".win-itembox:nth-child(2)").length); oldExecuteAnimations.call(listView.layout); }; // Insert one item listView.itemDataSource.list.splice(7, 0, { title: "new item", itemWidth: "80px", itemHeight: "80px" }); Helper.ListView.waitForDeferredAction(listView)().then(function () { // Eventually, we end up with only 51 realized items (as we started before the insert) LiveUnit.Assert.areEqual(51, getNumberOfItemsRealized()); VirtualizeContentsViewTestHost.removeChild(element); complete(); }); }); }; testAnimationInViewportPendingFlagTrue = function (complete) { generateAnimationInViewportPendingFlagTest(complete, 2, true); }; testAnimationInViewportPendingFlagFalse = function (complete) { generateAnimationInViewportPendingFlagTest(complete, 20, false); }; testSerializeRealizePasses = function (complete) { Helper.initUnhandledErrors(); var placeholder = createListViewElement(); var data = initData(), list = new WinJS.Binding.List(data); var listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: generateRenderer("100px"), layout: new WinJS.UI.ListLayout() }); placeholder.addEventListener("contentanimating", function (eventObject) { listView.ensureVisible(0); eventObject.preventDefault(); }); Helper.ListView.waitForDeferredAction(listView)(). then(Helper.validateUnhandledErrorsOnIdle). done(function () { VirtualizeContentsViewTestHost.removeChild(placeholder); complete(); }); }; testScrollDonotCancelAnimations = function (complete) { Helper.initUnhandledErrors(); var placeholder = createListViewElement(); var data = initData(), list = new WinJS.Binding.List(data); var animationStarted, dispose; var listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: generateRenderer("100px"), layout: { initialize: function (site, groups) { }, setupAnimations: function () { }, layout: function (tree, changedRange, modifiedElements, modifiedGroups) { }, executeAnimations: function () { return new WinJS.Promise( function init() { animationStarted = true; }, function error() { LiveUnit.Assert.isTrue(dispose, "Animation was canceled"); } ); }, } }); Helper.ListView.waitForReady(listView, -1)().then(function () { list.shift(); return waitForItemsLoaded(listView); }).then(function () { LiveUnit.Assert.isTrue(animationStarted); listView.ensureVisible(10); return waitForItemsLoaded(listView); }).then(function () { list.shift(); return WinJS.Promise.timeout(); }).then(function () { dispose = true; listView.dispose(); VirtualizeContentsViewTestHost.removeChild(placeholder); complete(); }); }; testNoProgressRingInEmptyView = function (complete) { var lv = new ListView(); VirtualizeContentsViewTestHost.appendChild(lv.element); var origDelay = WinJS.UI._LISTVIEW_PROGRESS_DELAY; WinJS.UI._LISTVIEW_PROGRESS_DELAY = 0; Helper.ListView.waitForReady(lv, 1000)().done(function () { LiveUnit.Assert.isFalse(VirtualizeContentsViewTestHost.querySelector("." + WinJS.UI._progressClass)); VirtualizeContentsViewTestHost.removeChild(lv.element); WinJS.UI._LISTVIEW_PROGRESS_DELAY = origDelay; lv.dispose(); complete(); }); }; testDeferContainerCreationUntilSeZoZoomCompletes = function (complete) { var wrapper = document.createElement("div"); wrapper.innerHTML = "
"; var sezoDiv = wrapper.firstElementChild; var data = []; for (var i = 0; i < 200; i++) { data.push({ data: "" + i }); } var list = new WinJS.Binding.List(data); var glist = list.createGrouped(function (item) { return item.data + ""; }, function (item) { return { data: item.data + "" }; }); var zoomedIn = new ListView(sezoDiv.children[0]); var zoomedOut = new ListView(sezoDiv.children[1]); zoomedOut.itemDataSource = glist.groups.dataSource; zoomedIn.itemDataSource = glist.dataSource; zoomedIn.groupDataSource = glist.groups.dataSource; zoomedIn.itemTemplate = zoomedOut.itemTemplate = function (itemPromise) { return itemPromise.then(function (item) { var div = document.createElement("div"); div.textContent = item.data.data; div.style.width = div.style.height = "200px"; return div; }); }; var sezo = new WinJS.UI.SemanticZoom(sezoDiv); var containerCountAtBegin = 0; var oldBeginZoom = zoomedIn._beginZoom; zoomedIn._beginZoom = function (item, position) { containerCountAtBegin = zoomedIn._view.containers.length; return oldBeginZoom.bind(zoomedIn, item, position)(); }; var oldEndZoom = zoomedIn._endZoom; zoomedIn._endZoom = function (isCurrentView) { LiveUnit.Assert.areEqual(containerCountAtBegin, zoomedIn._view.containers.length, "At least one container was created during zoom."); oldEndZoom.bind(zoomedIn, isCurrentView)(); WinJS.Utilities.disposeSubTree(wrapper); VirtualizeContentsViewTestHost.removeChild(wrapper); complete(); }; VirtualizeContentsViewTestHost.appendChild(wrapper); Helper.ListView.waitForReady(zoomedIn, -1)().then(function () { sezo.zoomedOut = true; }); }; testSlowHeaderRenderingDoesNotCrash = function (complete) { 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 "header"; }, function (item) { return { data: "header" }; }); var lv = new ListView(); lv.itemDataSource = glist.dataSource; lv.groupDataSource = glist.groups.dataSource; lv.groupHeaderTemplate = function (itemPromise) { return itemPromise.then(function (item) { var div = document.createElement("div"); div.className = "myHeader"; return WinJS.Promise.timeout(1000).then(function () { return div; }); }); }; VirtualizeContentsViewTestHost.appendChild(lv.element); Helper.ListView.waitForReady(lv, -1)().then(function () { LiveUnit.Assert.isTrue(VirtualizeContentsViewTestHost.querySelector(".myHeader")); VirtualizeContentsViewTestHost.removeChild(lv.element); lv.dispose(); complete(); }); }; testSlowHeaderRenderingDoesNotRenderDuplicateHeaders = function (complete) { var data = []; for (var i = 0; i < 100; i++) { data.push({ data: "" + i }); } var list = new WinJS.Binding.List(data); var glist = list.createGrouped(function (item) { return item.data + ""; }, function (item) { return { data: item.data + "" }; }); var lv = new ListView(); lv.element.style.width = "600px"; lv.itemDataSource = glist.dataSource; lv.groupDataSource = glist.groups.dataSource; lv.groupHeaderTemplate = function (itemPromise) { return itemPromise.then(function (item) { var div = document.createElement("div"); div.className = "myHeader" + item.data.data; div.style.width = "300px"; div.style.height = "200px"; div.innerHTML = item.data.data; return WinJS.Promise.timeout(1000).then(function () { return div; }); }); }; VirtualizeContentsViewTestHost.appendChild(lv.element); Helper.ListView.waitForReady(lv, -1)().then(function () { lv.scrollPosition = 2000; return Helper.ListView.waitForReady(lv, -1)(); }).then(function () { LiveUnit.Assert.areEqual(1, VirtualizeContentsViewTestHost.querySelectorAll(".myHeader24").length); VirtualizeContentsViewTestHost.removeChild(lv.element); lv.dispose(); complete(); }); }; testGetAdjacentWait = function (complete) { var itemsCount = 300, list = new WinJS.Binding.List(initData(itemsCount)); var myGroupedList = list.createGrouped(groupKey, groupData); var placeholder = createListViewElement(); var listView = new ListView(placeholder, { itemDataSource: myGroupedList.dataSource, groupDataSource: myGroupedList.groups.dataSource, itemTemplate: generateRenderer("100px"), groupHeaderTemplate: generateRenderer("50px"), layout: new WinJS.UI.GridLayout() }); if (!listView.layout['_usingStructuralNodes']) { placeholder.parentNode.removeChild(placeholder); complete(); return; } listView._view._createChunkWithBlocks = function (groups, count, blockSize, chunkSize) { Object.getPrototypeOf(listView._view)._createChunkWithBlocks.call(listView._view, groups, count, blockSize, chunkSize); return listView._view.containers.length >= 100; }; var jobNode; listView._view._scheduleLazyTreeCreation = function () { jobNode = Object.getPrototypeOf(listView._view)._scheduleLazyTreeCreation.call(listView._view); jobNode.pause(); return jobNode; }; return Helper.ListView.waitForReady(listView)().then(function () { var promise = listView._view._creatingContainersWork.promise; jobNode.resume(); return promise; }).then(function () { return listView._view.getAdjacent({ type: "item", index: itemsCount - 1 }, WinJS.Utilities.Key.upArrow); }).done(function () { placeholder.parentNode.removeChild(placeholder); complete(); }); }; testPanningUsingAsyncDataSourceWithMultiStageRenderers = function (complete) { function createDataSource() { var count = 10000; var dataSource = { itemsFromKey: function (key, countBefore, countAfter) { return this.itemsFromIndex(parseInt(key, 10), countBefore, countAfter); }, itemsFromIndex: function (index, countBefore, countAfter) { var requestedCount = countBefore + countAfter + 1; return WinJS.Promise.timeout(1200).then(function () { return new WinJS.Promise(function (complete, error) { if (index >= 0 && index < count) { var startIndex = Math.max(0, index - countBefore), endIndex = Math.min(index + Math.max(20, countAfter), count - 1), size = endIndex - startIndex + 1; var items = []; for (var i = startIndex; i < startIndex + size; i++) { items.push({ key: i.toString(), data: { title: i } }); } complete({ items: items, offset: index - startIndex, totalCount: count, absoluteIndex: index }); } else { complete({}); } }); }); }, getCount: function () { return WinJS.Promise.timeout(1200).then(function () { return WinJS.Promise.wrap(count); }); } }; return new WinJS.UI.ListDataSource(dataSource); } var element = document.createElement("div"); element.style.width = "600px"; element.style.height = "600px"; VirtualizeContentsViewTestHost.appendChild(element); var placeholdersLoadedSignal = new WinJS._Signal(); var listView = new ListView(element, { itemDataSource: createDataSource(), itemTemplate: function (itemPromise) { if (itemPromise.handle === "290") { placeholdersLoadedSignal.complete(); } var div = document.createElement("div"); div.style.width = "100px"; div.style.height = "100px"; div.innerHTML = "..."; return { element: div, renderComplete: itemPromise.then(function (item) { div.textContent = item.data.title; return item.ready.then(function () { return WinJS.Promise.timeout(1000).then(function () { div.style.backgroundColor = "red"; }); }); }) }; } }); Helper.ListView.waitForReady(listView, -1)().done(function () { listView.scrollPosition = 62800; placeholdersLoadedSignal.promise.then(function () { WinJS.Promise.timeout(100).then(function () { listView.scrollPosition = 62850; WinJS.Promise.timeout(500).then(function () { listView.scrollPosition = 62860; Helper.ListView.waitForDeferredAction(listView)().then(function () { var firstVisible = listView.indexOfFirstVisible; var lastVisible = listView.indexOfLastVisible; for (var i = firstVisible; i <= lastVisible; i++) { var item = listView.elementFromIndex(i); LiveUnit.Assert.areEqual("rgb(255, 0, 0)", getComputedStyle(item).backgroundColor); } VirtualizeContentsViewTestHost.removeChild(element); complete(); }); }); }); }); }); }; testAddingItemToTheEndOfListWhileLastItemHadFocusDoesNotLoseFocus = function (complete) { var data = []; for (var i = 0; i < 10; i++) { data.push({ data: "" + i }); } var list = new WinJS.Binding.List(data); var lv = new ListView(); lv.itemDataSource = list.dataSource; lv.itemTemplate = function (itemPromise) { return itemPromise.then(function (item) { var div = document.createElement("div"); div.style.width = "200px"; div.style.height = "100px"; div.innerHTML = item.data.data; return div; }); }; VirtualizeContentsViewTestHost.appendChild(lv.element); lv.element.focus(); Helper.ListView.waitForReady(lv, -1)().then(function () { var items = document.querySelectorAll(".win-item"); (items[items.length - 1]).focus(); return Helper.ListView.waitForReady(lv, -1)(); }).then(function () { list.splice(list.length - 1, 0, { data: "10" }); return Helper.ListView.waitForReady(lv, -1)(); }).done(function () { LiveUnit.Assert.isTrue(lv.element.contains(document.activeElement)); VirtualizeContentsViewTestHost.removeChild(lv.element); lv.dispose(); complete(); }); }; testDeleteWhileFocusIsOnLastItemDoesNotLoseFocus = function (complete) { var data = []; for (var i = 0; i < 390; i++) { data.push({ data: "" + i }); } var list = new WinJS.Binding.List(data); var lv = new ListView(); lv.itemDataSource = list.dataSource; lv.itemTemplate = function (itemPromise) { return itemPromise.then(function (item) { var div = document.createElement("div"); div.style.width = "100px"; div.style.height = "100px"; div.innerHTML = item.data.data; return div; }); }; lv.element.style.height = lv.element.style.width = "600px"; VirtualizeContentsViewTestHost.appendChild(lv.element); lv.element.focus(); Helper.ListView.waitForReady(lv, 1000)().then(function () { var items = document.querySelectorAll(".win-item"); lv.currentItem = { index: 389, hasFocus: true, showFocus: true }; lv.ensureVisible(389); return Helper.ListView.waitForReady(lv, -1)(); }).then(function () { list.dataSource.remove("0"); return Helper.ListView.waitForReady(lv, -1)(); }).done(function () { LiveUnit.Assert.isTrue(lv.element.contains(document.activeElement)); complete(); }); }; testAriaWorkerCancellation = function (complete) { Helper.initUnhandledErrors(); var data = []; for (var i = 0; i < 5000; i++) { data.push({ data: "" + i }); } var list = new WinJS.Binding.List(data); var glist = list.createGrouped(function (item) { return Math.floor(item.data / 10) + ""; }, function (item) { return { data: Math.floor(item.data / 10) + "" }; }); var lv = new ListView(); lv.itemDataSource = glist.dataSource; lv.groupDataSource = glist.groups.dataSource; lv.itemTemplate = function (itemPromise) { return itemPromise.then(function (item) { // Making each item tiny to fit many of them in one viewport, // giving aria lots of work so it cannot finish in a single // timeslice. var div = document.createElement("div"); div.style.width = "20px"; div.style.height = "10px"; div.innerHTML = item.data.data; return div; }); }; lv.element.style.width = "1500px"; lv.element.style.height = "1000px"; VirtualizeContentsViewTestHost.appendChild(lv.element); var realSetupAria = lv._view._setupAria; lv._view._setupAria = function (timedOut) { lv._view._setupAria = realSetupAria; var cancellationPromise = realSetupAria.call(lv._view, timedOut); // We do 1 WinJS.Utilities._setImmediate, allowing aria to schedule its belowNormal priority // task, since we have 100s of items per page aria worker should need another // timeslice to finish its job. But before it can enter its 2nd timeslice, // we scroll away from the current viewport and aria worker should get // canceled. WinJS.Utilities._setImmediate(function () { WinJS.Utilities.Scheduler.schedule(function () { // accessibilityannotationcomplete fires when aria completes, when that happens, // no errors should have been caught. var scrolled = false; lv.addEventListener("accessibilityannotationcomplete", function (ev) { // Sometimes there can be multiple event callbacks, we want the one where aria is truly finished var firstIndex = ev.detail.firstIndex; var lastIndex = ev.detail.lastIndex; if (firstIndex !== lastIndex) { Helper.validateUnhandledErrorsOnIdle(). done(function annotationcomplete() { LiveUnit.Assert.isTrue(scrolled, "Test completed before scroll"); VirtualizeContentsViewTestHost.removeChild(lv.element); lv.dispose(); complete(); }); } }); lv.scrollPosition = 20 * 1000; scrolled = true; }, WinJS.Utilities.Scheduler.Priority.normal); }); return cancellationPromise; }; }; testDeleteDoesNotLoseFocusRectangle = 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 lv = new ListView(); lv.itemDataSource = list.dataSource; lv.itemTemplate = function (itemPromise) { return itemPromise.then(function (item) { var div = document.createElement("div"); div.textContent = item.data.data; div.style.width = div.style.height = "200px"; div.style.margin = "5px"; return div; }); }; VirtualizeContentsViewTestHost.appendChild(lv.element); Helper.ListView.waitForReady(lv, -1)().then(function () { lv.currentItem = { index: 0, hasFocus: true, showFocus: true }; return Helper.ListView.waitForReady(lv, -1)(); }).then(function () { LiveUnit.Assert.isTrue(document.querySelector("." + WinJS.UI._itemFocusOutlineClass), "itemFocusOutline not drawn. document.hasFocus(): " + document.hasFocus() + ". document requires focus to pass"); list.shift(); return Helper.ListView.waitForReady(lv, -1)(); }).done(function () { LiveUnit.Assert.isTrue(document.querySelector("." + WinJS.UI._itemFocusOutlineClass), "itemFocusOutline not drawn. document.hasFocus(): " + document.hasFocus() + ". document requires focus to pass"); complete(); }); }; testRealizeRetryDuringEdits = function (complete) { Helper.initUnhandledErrors(); var list = new WinJS.Binding.List(initData()); var placeholder = createListViewElement("400px"); var listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: generateRenderer("100px"), layout: new WinJS.UI.GridLayout() }); Helper.ListView.skipFirstAnimation(listView); Helper.ListView.waitForReady(listView, -1)().then(function () { listView._versionManager.beginUpdating(); listView.scrollPosition = 900; return WinJS.Promise.timeout(100); }).then(function () { listView._versionManager.endUpdating(); return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { LiveUnit.Assert.areEqual(900, listView.scrollPosition); return Helper.validateUnhandledErrorsOnIdle(); }).then(function () { placeholder.parentNode.removeChild(placeholder); complete(); }); }; testUpdateContainersUpdatesToAffectedRange = function (complete) { if (!Helper.Browser.supportsCSSGrid) { complete(); return; } Helper.initUnhandledErrors(); var count = 20, list = new WinJS.Binding.List(initData(count)); function itemInfo(index) { return { width: 300, height: 300 }; } function groupInfo(group) { return { enableCellSpanning: true, cellWidth: 300, cellHeight: 300 }; } function getRandomColor() { return "rgb(" + Math.floor(Math.random() * 256) + "," + Math.floor(Math.random() * 256) + "," + Math.floor(Math.random() * 256) + ")"; } function renderer(itemPromise) { return itemPromise.then(function (item) { var element = document.createElement("div"); element.className = "myVVTestClass"; element.textContent = item.data.title; element.style.width = element.style.height = "300px"; element.style.backgroundColor = getRandomColor(); return element; }); } var placeholder = createListViewElement("300px"); var listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: renderer, layout: { type: WinJS.UI.CellSpanningLayout, groupInfo: groupInfo, itemInfo: itemInfo } }); Helper.ListView.skipFirstAnimation(listView); Helper.ListView.waitForReady(listView, -1)().then(function () { listView.ensureVisible(10); return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { list.splice(5, 5); count -= 5; return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { var containers = placeholder.querySelectorAll(".win-container"); LiveUnit.Assert.areEqual(count, containers.length); for (var i = 0; i < count; i++) { var c = containers[i]; LiveUnit.Assert.areEqual(i + 1, +c.style.msGridColumn); } return Helper.validateUnhandledErrorsOnIdle(); }).then(function () { placeholder.parentNode.removeChild(placeholder); complete(); }); }; testDefaultPagesToPrefetch = function (complete) { // Verifies the correct default values for ListView.maxTrailingPages and ListView.maxLeadingPages are used when no values are set by the user. WinJS.Utilities._setIsiOS(false); var viewPortHeight = 300, count = 200, list = new WinJS.Binding.List(initData(count)); function renderer(itemPromise) { return itemPromise.then(function (item) { var element = document.createElement("div"); element.textContent = item.data.title; element.style.width = element.style.height = "100px"; return element; }); } var placeholder = createListViewElement(viewPortHeight + "px"); var listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: renderer, layout: { type: WinJS.UI.ListLayout }, }); var expectedLeadingPages = listView.maxLeadingPages, expectedTrailingPages = listView.maxTrailingPages; validatePagesToPrefetch(listView, expectedLeadingPages, expectedTrailingPages, viewPortHeight).done(complete); }; testIOSDefaultPagesToPrefetch = function (complete) { // Verifies that when running in IOS the correct IOS default values for ListView.maxTrailingPages and ListView.maxLeadingPages are used. WinJS.Utilities._setIsiOS(true); var viewPortHeight = 300, count = 200, list = new WinJS.Binding.List(initData(count)); function renderer(itemPromise) { return itemPromise.then(function (item) { var element = document.createElement("div"); element.textContent = item.data.title; element.style.width = element.style.height = "100px"; return element; }); } var placeholder = createListViewElement(viewPortHeight + "px"); var listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: renderer, layout: { type: WinJS.UI.ListLayout }, }); var expectedLeadingPages = WinJS.UI._VirtualizeContentsView._iOSMaxLeadingPages, expectedTrailingPages = WinJS.UI._VirtualizeContentsView._iOSMaxTrailingPages; validatePagesToPrefetch(listView, expectedLeadingPages, expectedTrailingPages, viewPortHeight).done(complete); }; testCustomPagesToPrefetch = function (complete) { var viewPortHeight = 300, count = 200, list = new WinJS.Binding.List(initData(count)); var expectedLeadingPages = 0, expectedTrailingPages = 0; function renderer(itemPromise) { return itemPromise.then(function (item) { var element = document.createElement("div"); element.textContent = item.data.title; element.style.width = element.style.height = "100px"; return element; }); } var placeholder = createListViewElement(viewPortHeight + "px"); var listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: renderer, layout: { type: WinJS.UI.ListLayout }, maxLeadingPages: expectedLeadingPages, maxTrailingPages: expectedTrailingPages, }); validatePagesToPrefetch(listView, expectedLeadingPages, expectedTrailingPages, viewPortHeight).done(complete); }; testPrefetchAllPages = function (complete) { // Test that it is possible to turn off virtualization by setting ListView.maxLeadingPages and ListView.maxTrailingPages to Number.MAX_VALUE var viewPortHeight = 300, count = 200, list = new WinJS.Binding.List(initData(count)); var expectedLeadingPages = Number.MAX_VALUE, expectedTrailingPages = Number.MAX_VALUE; function renderer(itemPromise) { return itemPromise.then(function (item) { var element = document.createElement("div"); element.textContent = item.data.title; element.style.width = element.style.height = "100px"; return element; }); } var placeholder = createListViewElement(viewPortHeight + "px"); var listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: renderer, layout: { type: WinJS.UI.ListLayout }, maxLeadingPages: expectedLeadingPages, maxTrailingPages: expectedTrailingPages, }); Helper.ListView.waitForReady(listView, -1)().then(function () { // Verify property getters return the correct value. LiveUnit.Assert.areEqual(expectedLeadingPages, listView.maxLeadingPages); LiveUnit.Assert.areEqual(expectedTrailingPages, listView.maxTrailingPages); // With no virtualization we expect all items to be realized. var expectedRealizedCount = count; LiveUnit.Assert.areEqual(expectedRealizedCount, listView.element.querySelectorAll(".win-container:not(.win-backdrop)").length, "With no virtualization, we expect all items to be realized."); }).done(complete); }; // Some browsers do not have good FlexBox behavior so we have to disable // structure nodes. testStructureNodesUsedWhenSupported = function (complete) { var element = document.createElement("div"); element.style.width = "300px"; element.style.height = "300px"; VirtualizeContentsViewTestHost.appendChild(element); var list = createBindingList(100); var listView = new ListView(element, { layout: new WinJS.UI.GridLayout(), itemDataSource: list.dataSource, itemTemplate: fixedSizeTemplate }); Helper.ListView.waitForReady(listView)().then(function () { var usingStructuralNodes = !!(listView._view.tree[0].itemsContainer.itemsBlocks); LiveUnit.Assert.areEqual(usingStructuralNodes, listView.layout['_usingStructuralNodes']); element.parentNode.removeChild(element); complete(); }); }; testDeferUnrealizingUntilSeZoZoomCompletes = function (complete) { var wrapper = document.createElement("div"); wrapper.innerHTML = "
"; var sezoDiv = wrapper.firstElementChild; var data = []; for (var i = 0; i < 200; i++) { data.push({ data: "" + i }); } var list = new WinJS.Binding.List(data); var glist = list.createGrouped(function (item) { return item.data + ""; }, function (item) { return { data: item.data + "" }; }); var zoomedIn = new ListView(sezoDiv.children[0]); var zoomedOut = new ListView(sezoDiv.children[1]); zoomedOut.itemDataSource = glist.groups.dataSource; zoomedIn.itemDataSource = glist.dataSource; zoomedIn.groupDataSource = glist.groups.dataSource; zoomedIn.itemTemplate = zoomedOut.itemTemplate = function (itemPromise) { return itemPromise.then(function (item) { var div = document.createElement("div"); div.textContent = item.data.data; div.style.width = div.style.height = "200px"; return div; }); }; var sezo = new WinJS.UI.SemanticZoom(sezoDiv); var unrealizeCount = 0; var oldUnrealizeItem = zoomedIn._view._unrealizeItem; zoomedIn._view._unrealizeItem = function (index) { unrealizeCount++; oldUnrealizeItem.bind(zoomedIn._view, index)(); }; var oldEndZoom = zoomedIn._endZoom; zoomedIn._endZoom = function (isCurrentView) { LiveUnit.Assert.areEqual(0, unrealizeCount, "At least one item was unrealized before zoom animation finished."); oldEndZoom.bind(zoomedIn, isCurrentView)(); WinJS.Utilities.disposeSubTree(wrapper); VirtualizeContentsViewTestHost.removeChild(wrapper); complete(); }; VirtualizeContentsViewTestHost.appendChild(wrapper); Helper.ListView.waitForReady(zoomedIn, -1)().then(function () { zoomedIn.scrollPosition = Number.MAX_VALUE; sezo.zoomedOut = true; }); }; } var generateEditsDonotCreateAllContainersTest = function (groups, structureNodes) { VirtualizedViewTests.prototype["testEditsDonotCreateAllContainers" + (groups ? "WithGroups" : "") + (structureNodes ? "WithStructureNodes" : "")] = function (complete) { WinJS.UI._VirtualizeContentsView._chunkSize = 100; var itemsCount = BIG_DATASET, data = initData(itemsCount, 250); var placeholder = createListViewElement(); var list = new WinJS.Binding.List(data), groupedList = list.createGrouped(groupKey, groupData); var cellSpanningOptions = { groupInfo: { enableCellSpanning: true, cellWidth: 100, cellHeight: 100 }, itemInfo: function (itemIndex) { return { width: 100, height: 100 }; } }; var layout; if (structureNodes) { layout = new ListLayout(); } else { layout = new WinJS.UI.CellSpanningLayout(cellSpanningOptions); } var listView = new ListView(placeholder, { itemDataSource: groups ? groupedList.dataSource : list.dataSource, groupDataSource: groups ? groupedList.groups.dataSource : null, itemTemplate: generateRenderer("100px"), groupHeaderTemplate: generateRenderer("50px"), layout: layout }); listView._view._createChunkWithBlocks = function (groups, count, blockSize, chunkSize) { Object.getPrototypeOf(listView._view)._createChunkWithBlocks.call(listView._view, groups, count, blockSize, chunkSize); if (listView._view.containers.length >= 100) { return true; } }; listView._view._createChunk = function (groups, count, chunkSize) { Object.getPrototypeOf(listView._view)._createChunk.call(listView._view, groups, count, chunkSize); if (listView._view.containers.length >= 100) { return true; } }; var jobNode; listView._view._scheduleLazyTreeCreation = function () { jobNode = Object.getPrototypeOf(listView._view)._scheduleLazyTreeCreation.call(listView._view); jobNode.pause(); return jobNode; }; return Helper.ListView.waitForReady(listView)().then(function () { LiveUnit.Assert.isTrue(structureNodes || 100 === placeholder.querySelectorAll(".win-container").length); LiveUnit.Assert.areEqual(100, listView._view.containers.length); list.splice(0, 0, { title: "NewTile", group: 0 }); return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { LiveUnit.Assert.isTrue(structureNodes || 101 === placeholder.querySelectorAll(".win-container").length); LiveUnit.Assert.areEqual(101, listView._view.containers.length); list.splice(0, 11); return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { LiveUnit.Assert.isTrue(structureNodes || 101 === placeholder.querySelectorAll(".win-container").length); LiveUnit.Assert.areEqual(101, listView._view.containers.length); list.splice(10000, 0, { title: "NewTile", group: 40 }); return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { LiveUnit.Assert.isTrue(structureNodes || 101 === placeholder.querySelectorAll(".win-container").length); LiveUnit.Assert.areEqual(101, listView._view.containers.length); list.splice(10000, 200); return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { LiveUnit.Assert.isTrue(structureNodes || 101 === placeholder.querySelectorAll(".win-container").length); LiveUnit.Assert.areEqual(101, listView._view.containers.length); itemsCount = 1000; data = initData(itemsCount, 10); list = new WinJS.Binding.List(data); if (groups) { groupedList = list.createGrouped(groupKey, groupData); listView.itemDataSource = groupedList.dataSource; listView.groupDataSource = groupedList.groups.dataSource; } else { listView.itemDataSource = list.dataSource; } return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { LiveUnit.Assert.isTrue(structureNodes || 100 === placeholder.querySelectorAll(".win-container").length); LiveUnit.Assert.areEqual(100, listView._view.containers.length); list.splice(0, 0, { title: "NewTile", group: 0 }); return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { LiveUnit.Assert.isTrue(structureNodes || 101 === placeholder.querySelectorAll(".win-container").length); LiveUnit.Assert.areEqual(101, listView._view.containers.length); list.splice(0, 6); return Helper.ListView.waitForReady(listView, -1)(); }).then(function () { LiveUnit.Assert.isTrue(structureNodes || 101 === placeholder.querySelectorAll(".win-container").length); LiveUnit.Assert.areEqual(101, listView._view.containers.length); list.splice(70, 5); return Helper.ListView.waitForReady(listView, -1)(); }).done(function () { LiveUnit.Assert.isTrue(structureNodes || 101 === placeholder.querySelectorAll(".win-container").length); LiveUnit.Assert.areEqual(101, listView._view.containers.length); placeholder.parentNode.removeChild(placeholder); complete(); }); } }; generateEditsDonotCreateAllContainersTest(false, false); generateEditsDonotCreateAllContainersTest(true, false); generateEditsDonotCreateAllContainersTest(false, true); generateEditsDonotCreateAllContainersTest(true, true); var generateNoFocusLossAfterDeleteTest = function (layout) { VirtualizedViewTests.prototype["testNoFocusLossAfterDelete" + layout] = function (complete) { var data = []; for (var i = 0; i < 10; i++) { data.push({ data: i }); } var list = new WinJS.Binding.List(data); var lv = new ListView(); lv.layout = new WinJS.UI[layout]; lv.itemDataSource = list.dataSource; VirtualizeContentsViewTestHost.appendChild(lv.element); Helper.ListView.waitForReady(lv, 1000)().then(function () { lv.currentItem = { type: WinJS.UI.ObjectType.item, index: 2, hasFocus: true }; return Helper.ListView.waitForReady(lv, -1)(); }).then(function () { lv.itemDataSource.remove("2"); return Helper.ListView.waitForReady(lv, -1)(); }).done(function () { LiveUnit.Assert.isTrue(document.activeElement); LiveUnit.Assert.isTrue((document.activeElement).classList.contains(WinJS.UI._itemClass)); LiveUnit.Assert.isTrue((document.activeElement).contains(lv.elementFromIndex(2))); VirtualizeContentsViewTestHost.removeChild(lv.element); complete(); }); }; }; generateNoFocusLossAfterDeleteTest("GridLayout"); generateNoFocusLossAfterDeleteTest("ListLayout"); if (Helper.Browser.supportsCSSGrid) { generateNoFocusLossAfterDeleteTest("CellSpanningLayout"); } var generateAnimationDuringSezoZoomingTests = function generateAnimationDuringSezoZoomingTests(operation) { var scrollToEnd; var operationArgs; if (operation === "remove") { scrollToEnd = true; operationArgs = ["0"]; } else if (operation === "insert") { scrollToEnd = false; operationArgs = [null, { data: "-1" }] } else if (operation === "change") { scrollToEnd = true; operationArgs = ["0", { data: "99" }]; } else { return; } VirtualizedViewTests.prototype["testAnimationDuringSezoZoomingAnd" + operation] = function (complete) { var wrapper = document.createElement("div"); wrapper.innerHTML = "
"; var sezoDiv = wrapper.firstElementChild; var data = []; for (var i = 0; i < 200; i++) { data.push({ data: "" + i }); } var list = new WinJS.Binding.List(data); var glist = list.createGrouped(function (item) { return item.data + ""; }, function (item) { return { data: item.data + "" }; }); var zoomedIn = new ListView(sezoDiv.children[0]); var zoomedOut = new ListView(sezoDiv.children[1]); zoomedOut.itemDataSource = glist.groups.dataSource; zoomedIn.itemDataSource = glist.dataSource; zoomedIn.groupDataSource = glist.groups.dataSource; zoomedIn.itemTemplate = zoomedOut.itemTemplate = function (itemPromise) { return itemPromise.then(function (item) { var div = document.createElement("div"); div.textContent = item.data.data; div.style.width = div.style.height = "200px"; return div; }); }; var sezo = new WinJS.UI.SemanticZoom(sezoDiv); VirtualizeContentsViewTestHost.appendChild(wrapper); Helper.ListView.waitForReady(zoomedIn, -1)().then(function () { if (scrollToEnd) { zoomedIn.scrollPosition = Number.MAX_VALUE; } return Helper.ListView.waitForReady(zoomedIn, -1)(); }).then(function () { glist.dataSource[operation].apply(glist.dataSource, operationArgs); sezo.zoomedOut = true; return Helper.ListView.waitForReady(zoomedIn, -1)(); }).done(function () { WinJS.Utilities.disposeSubTree(wrapper); VirtualizeContentsViewTestHost.removeChild(wrapper); complete(); }); } }; generateAnimationDuringSezoZoomingTests("remove"); generateAnimationDuringSezoZoomingTests("insertAtStart"); generateAnimationDuringSezoZoomingTests("change"); function generateDomTrimTest(name, data, groups, scrollbarPos, viewportHeight, verify) { VirtualizedViewTests.prototype["testDomTrim" + name] = function (complete) { WinJS.UI._VirtualizeContentsView._maxTimePerCreateContainers = Number.MAX_VALUE; WinJS.UI._VirtualizeContentsView._defaultPagesToPrefetch = 0; WinJS.UI._VirtualizeContentsView._disableCustomPagesPrefetch = true; var placeholder = createListViewElement(); placeholder.id = "DomTrimTest"; placeholder.style.height = viewportHeight + "px"; placeholder.addEventListener("contentanimating", function (eventObject) { eventObject.preventDefault(); }); var layout = { initialize: function (site, groups) { this._site = site; return "vertical"; }, layout: function (tree) { for (var i = 0; i < tree.length; i++) { var itemsContainer = tree[i].itemsContainer; var count = itemsContainer.itemsBlocks.reduce(function (previous, block) { return previous + block.items.length; }, 0); itemsContainer.element.style.height = (count * itemHeight) + "px"; } }, itemsFromRange: function (start, end) { return { firstIndex: Math.floor(start / itemHeight), lastIndex: Math.floor(end / itemHeight) }; }, numberOfItemsPerItemsBlock: numberOfItemsPerItemsBlock }; var list = new WinJS.Binding.List(data), listView: WinJS.UI.PrivateListView; if (groups) { var groupedList = list.createGrouped(groupKey, groupData), listView = new ListView(placeholder, { itemDataSource: groupedList.dataSource, groupDataSource: groupedList.groups.dataSource, itemTemplate: generateRenderer(itemHeight + "px"), groupHeaderTemplate: function () { var element = document.createElement("div"); element.style.display = "none"; return element; }, layout: layout }); } else { listView = new ListView(placeholder, { itemDataSource: list.dataSource, itemTemplate: generateRenderer(itemHeight + "px"), layout: layout }); } Helper.ListView.waitForReady(listView)().then(function () { listView.scrollPosition = scrollbarPos; return Helper.ListView.waitForDeferredAction(listView)(); }).then(function () { return verify(listView); }).then(function () { VirtualizeContentsViewTestHost.removeChild(placeholder); complete(); }); }; } generateDomTrimTest("OneBlock", initData(), false, 0, 9 * itemHeight, function (listView) { LiveUnit.Assert.areEqual(10, listView.element.querySelectorAll(".win-container").length); verifyBlockInDom(listView, [0]); }); generateDomTrimTest("SecondBlock", initData(), false, 10 * itemHeight, 10 * itemHeight, function (listView) { LiveUnit.Assert.areEqual(10, listView.element.querySelectorAll(".win-container").length); verifyBlockInDom(listView, [1]); }); generateDomTrimTest("InMiddleOfBlock", initData(), false, 5 * itemHeight, 5 * itemHeight, function (listView) { LiveUnit.Assert.areEqual(10, listView.element.querySelectorAll(".win-container").length); verifyBlockInDom(listView, [0]); }); generateDomTrimTest("InMiddleOfTwoBlocks", initData(), false, 5 * itemHeight, 10 * itemHeight, function (listView) { LiveUnit.Assert.areEqual(20, listView.element.querySelectorAll(".win-container").length); verifyBlockInDom(listView, [0, 1]); }); generateDomTrimTest("OneBlockWithGroup", initGroups([20, 20]), true, 0, 10 * itemHeight, function (listView) { LiveUnit.Assert.areEqual(10, listView.element.querySelectorAll(".win-container").length); verifyBlockInDom(listView, [0]); }); generateDomTrimTest("FirstBlockInGroup", initGroups([15, 20]), true, 0, 10 * itemHeight, function (listView) { LiveUnit.Assert.areEqual(10, listView.element.querySelectorAll(".win-container").length); verifyBlockInDom(listView, [0]); }); generateDomTrimTest("CollapseWholeGroup", initGroups([20, 20, 20]), true, 20 * itemHeight, 10 * itemHeight, function (listView) { LiveUnit.Assert.areEqual(10, listView.element.querySelectorAll(".win-container").length); verifyBlockInDom(listView, [2]); }); generateDomTrimTest("CollapsePartOfGroup", initGroups([15, 20, 20]), true, 15 * itemHeight, 10 * itemHeight, function (listView) { LiveUnit.Assert.areEqual(10, listView.element.querySelectorAll(".win-container").length); verifyBlockInDom(listView, [2]); }); generateDomTrimTest("BlocksAcrossGroups", initGroups([20, 20, 20]), true, 35 * itemHeight, 20 * itemHeight, function (listView) { LiveUnit.Assert.areEqual(30, listView.element.querySelectorAll(".win-container").length); verifyBlockInDom(listView, [3, 4, 5]); }); generateDomTrimTest("SimpleScroll", initData(), false, 0, 30 * itemHeight, function (listView) { LiveUnit.Assert.areEqual(30, listView.element.querySelectorAll(".win-container").length); verifyBlockInDom(listView, [0, 1, 2]); listView.scrollPosition = 10 * itemHeight; return Helper.ListView.waitForDeferredAction(listView)().then(function () { LiveUnit.Assert.areEqual(30, listView.element.querySelectorAll(".win-container").length); verifyBlockInDom(listView, [1, 2, 3]); listView.scrollPosition = 30 * itemHeight; return Helper.ListView.waitForDeferredAction(listView)(); }).then(function () { LiveUnit.Assert.areEqual(30, listView.element.querySelectorAll(".win-container").length); verifyBlockInDom(listView, [3, 4, 5]); listView.scrollPosition = 20 * itemHeight; return Helper.ListView.waitForDeferredAction(listView)(); }).then(function () { LiveUnit.Assert.areEqual(30, listView.element.querySelectorAll(".win-container").length); verifyBlockInDom(listView, [2, 3, 4]); }); }); generateDomTrimTest("NoGroupOverlapDuringScroll", initGroups([20, 20, 20, 20]), true, 0, 10 * itemHeight, function (listView) { LiveUnit.Assert.areEqual(10, listView.element.querySelectorAll(".win-container").length); verifyBlockInDom(listView, [0]); listView.scrollPosition = 40 * itemHeight; return Helper.ListView.waitForReady(listView, -1)().then(function () { LiveUnit.Assert.areEqual(10, listView.element.querySelectorAll(".win-container").length); verifyBlockInDom(listView, [4]); }); }); generateDomTrimTest("GroupOverlapDuringScroll", initGroups([20, 20, 20, 20, 20]), true, 0, 60 * itemHeight, function (listView) { LiveUnit.Assert.areEqual(60, listView.element.querySelectorAll(".win-container").length); verifyBlockInDom(listView, [0, 1, 2, 3, 4, 5]); listView.scrollPosition = 30 * itemHeight; return Helper.ListView.waitForDeferredAction(listView)().then(function () { LiveUnit.Assert.areEqual(60, listView.element.querySelectorAll(".win-container").length); verifyBlockInDom(listView, [3, 4, 5, 6, 7, 8]); listView.scrollPosition = 10 * itemHeight; return Helper.ListView.waitForDeferredAction(listView)(); }).then(function () { LiveUnit.Assert.areEqual(60, listView.element.querySelectorAll(".win-container").length); verifyBlockInDom(listView, [1, 2, 3, 4, 5, 6]); }); }); generateDomTrimTest("ensureVisibleWithoutMove", initData(), false, 500, 1000, function (listView) { LiveUnit.Assert.areEqual(20, listView.element.querySelectorAll(".win-container").length); verifyBlockInDom(listView, [1, 2]); listView.ensureVisible(15); return Helper.ListView.waitForDeferredAction(listView)().then(function () { LiveUnit.Assert.areEqual(20, listView.element.querySelectorAll(".win-container").length); verifyBlockInDom(listView, [1, 2]); }); }); // Begin Stripe Testing function generateTestContainerStripesAfterConstruction(name, getLayout, getDataSources) { VirtualizedViewTests.prototype["testContainerStripesAfterConstruction" + name] = function (complete) { var layout = getLayout(); var dataSources = getDataSources(); var placeholder = createListViewElement(); var listView = new WinJS.UI.ListView(placeholder, { layout: layout, itemDataSource: dataSources.itemDataSource, groupDataSource: dataSources.groupDataSource, itemTemplate: generateRenderer("100px"), groupHeaderTemplate: generateRenderer("50px") }); Helper.ListView.waitForAllContainers(listView).then(function () { verifyContainerStripesByIndex(listView); placeholder.parentNode.removeChild(placeholder); complete(); }); } } function generateTestContainerStripesAfterEdits(name, getLayout, getDataSources) { var layout = getLayout(); if (!layout['_usingStructuralNodes']) { return; } VirtualizedViewTests.prototype["testContainerStripesAfterEdits" + name] = function (complete) { var dataSources = getDataSources(); var placeholder = createListViewElement(); var ListView = WinJS.UI.ListView; var listView = new ListView(placeholder, { layout: layout, itemDataSource: dataSources.itemDataSource, groupDataSource: dataSources.groupDataSource, itemTemplate: generateRenderer("100px"), groupHeaderTemplate: generateRenderer("50px") }); var list = listView.itemDataSource.list; LiveUnit.Assert.isTrue(list.length >= 100, "Test requires a data set of 100 or more items"); return Helper.ListView.waitForAllContainers(listView).then(function () { list.move(0, 10); list.move(70, 9); list.move(4, 3); return Helper.ListView.waitForAllContainers(listView); }).then(function () { verifyContainerStripesByIndex(listView); list.splice(25, 1); list.splice(10, 12); list.shift(); list.pop(); return Helper.ListView.waitForAllContainers(listView); }).then(function () { verifyContainerStripesByIndex(listView); list.unshift({ title: "N1", group: 0 }); list.unshift({ title: "N0", group: 0 }); list.push({ title: "Z1", group: 3, }); list.splice(50, 0, { title: "X1", group: 2, }); initData(25).forEach(function (datum) { list.splice(33, 0, datum); }); return Helper.ListView.waitForAllContainers(listView); }).then(function () { verifyContainerStripesByIndex(listView); placeholder.parentNode.removeChild(placeholder); complete(); }); } } function generateStripeTests(configuration) { var getLayout = configuration.layoutConfiguration.getter; var getDataSources = configuration.dataConfiguration.getter; var name = "_" + configuration.layoutConfiguration.descriptor + "_" + configuration.dataConfiguration.descriptor; generateTestContainerStripesAfterConstruction(name, getLayout, getDataSources); generateTestContainerStripesAfterEdits(name, getLayout, getDataSources); } // Generate stripe tests getPairWiseConfigurationsForSmallDataStripeTests().forEach(generateStripeTests); getPairWiseConfigurationsForBigDataStripeTests().forEach(generateStripeTests); } // register the object as a test class by passing in the name LiveUnit.registerTestClass("WinJSTests.VirtualizedViewTests");