// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License.txt in the project root for license information.
//
//
///
//
module WinJSTests {
"use strict";
var testRootEl,
newItems,
disposedItemsCount,
disposedItems,
actionHistory = {}; // Stores dispose/render history for each item
var ListView = WinJS.UI.ListView;
function pushAction(item, action) {
if (!actionHistory[item.index]) {
actionHistory[item.index] = [];
}
actionHistory[item.index].push(action);
}
function checkDisposeBeforeRender(index) {
Helper.ListView.elementsEqual(["dispose", "render"], actionHistory[index]);
}
function setupListView(element, layoutName) {
function groupKey(data) {
return data.group;
}
function groupData(data) {
return { title: data.group };
}
function createRenderer() {
return function renderer(itemPromise) {
return itemPromise.then(function (item) {
var element = document.createElement("div");
element.style.width = element.style.height = "100px";
element.textContent = item.data.title;
return element;
});
};
}
var items = [];
for (var i = 0; i < 100; i++) {
var gKey = String.fromCharCode("A".charCodeAt(0) + Math.floor(i / 10));
items.push({ group: gKey, title: "Tile" + gKey + i });
}
var list = new WinJS.Binding.List(items).createGrouped(groupKey, groupData);
return new ListView(element, {
layout: new WinJS.UI[layoutName](),
itemDataSource: list.dataSource,
itemTemplate: createRenderer(),
groupDataSource: list.groups.dataSource,
groupHeaderTemplate: createRenderer()
});
}
function itemRenderer(id, async?) {
var source: any = document.getElementById(id).cloneNode(true);
source.id = "";
return function itemRenderer(itemPromise): any {
newItems++;
var element = source.cloneNode(true);
element.msRendererPromise =
itemPromise.
then(function (item) {
WinJS.Utilities.markDisposable(element, function () {
pushAction(item, "dispose");
disposedItems.push(item.data.title);
});
pushAction(item, "render");
element.myDataConnectionExpando = item.data.id;
element.children[0].textContent = item.data.title;
});
if (async) {
return WinJS.Promise.timeout(Math.floor(Math.random() * 1000)).then(function () {
return element;
});
} else {
return element;
}
};
}
function checkSelection(listview, index, selected) {
var tile = listview.elementFromIndex(index).parentNode;
LiveUnit.Assert.areEqual(selected, WinJS.Utilities.hasClass(tile, WinJS.UI._selectedClass));
}
function checkTile(listview, index) {
var tile = listview.elementFromIndex(index),
container = Helper.ListView.containerFrom(tile),
left = Math.floor(index / 3) * 100,
top = (index - 3 * Math.floor(index / 3)) * 100;
LiveUnit.Assert.areEqual("Tile" + index, tile.textContent);
LiveUnit.Assert.areEqual(left, Helper.ListView.offsetLeftFromSurface(listview, container), "Error in tile " + index);
LiveUnit.Assert.areEqual(top, Helper.ListView.offsetTopFromSurface(listview, container), "Error in tile " + index);
}
function testDispose(layoutName, complete) {
var element = document.getElementById("reuseTestPlaceholder"),
myData = new WinJS.Binding.List();
for (var i = 0; i < 300; i++) {
myData.push({ title: i });
}
var listview = new WinJS.UI.ListView(element, {
layout: new WinJS.UI[layoutName](),
itemDataSource: myData.dataSource,
itemTemplate: itemRenderer("reuseTestTemplate"),
});
var expectedReleasedItems = [];
var tests = [
// Make sure the initial state is what we are expecting
function () {
var expected = 9 * 3;
LiveUnit.Assert.areEqual(expected, Helper.ListView.getRealizedCount(element));
LiveUnit.Assert.areEqual(expected, newItems);
LiveUnit.Assert.areEqual(0, disposedItemsCount);
Helper.ListView.elementsEqual([], disposedItems);
},
// In the following tests, note that:
// 0, 7, and 26 are realized items so manipulating them should trigger calls to dispose.
// 45 and 100 are unrealized items so manipulating them should not trigger calls to dispose.
// Ensure changing an item in the data source triggers a call to dispose
function () {
function checkChangeItem(index, newData, additionalDisposeItems) {
myData.setAt(index, newData);
expectedReleasedItems = expectedReleasedItems.concat(additionalDisposeItems);
Helper.ListView.elementsEqual(expectedReleasedItems, disposedItems);
// If the item was disposed, ensure it was disposed before it was rendered
if (additionalDisposeItems.length > 0) {
checkDisposeBeforeRender(index);
}
actionHistory = {};
}
actionHistory = {};
checkChangeItem(0, { title: "Zero" }, [0]);
checkChangeItem(45, { title: "Fourty-five" }, []);
checkChangeItem(26, { title: "Twenty-six" }, [26]);
checkChangeItem(7, { title: "Seven" }, [7]);
checkChangeItem(100, { title: "One Hundred" }, []);
},
// Ensure removing items from the data source triggers a call to dispose
function () {
function removeItems(startIndex, howMany, additionalDisposeItems) {
myData.splice(startIndex, howMany);
expectedReleasedItems = expectedReleasedItems.concat(additionalDisposeItems);
}
removeItems(45, 1, []);
removeItems(26, 1, ["Twenty-six"]);
removeItems(0, 1, ["Zero"]);
removeItems(1, 10, [2, 3, 4, 5, 6, "Seven", 8, 9, 10, 11]);
removeItems(100, 1, []);
Helper.ListView.waitForDeferredAction(listview)().then(function () {
// Unlike change notifications, remove notifications don't call dispose until
// the animation completes.
Helper.ListView.elementsEqual(expectedReleasedItems.sort(), disposedItems.sort());
complete();
});
}
];
Helper.ListView.runTests(listview, tests);
};
// Check that items, group headers, and grouping elements are not leaked in the DOM
// after the user scrolls. For example, check that there isn't a win-item in the DOM
// which isn't associated with any of the realized items.
function domCleanupAfterScrollingTest(layoutName, complete) {
function expectedNumberOfItemsInDom() {
var count = 0;
listView._view.items.each(function (index, itemElement, itemData) {
count++;
});
return count;
}
function expectedNumberOfGroupsInDom() {
return listView._groups.groups.reduce(function (count, group) {
return group.elements || group.header ? count + 1 : count;
}, 0);
}
var newNode = document.createElement("div");
newNode.style.width = "300px";
newNode.style.height = "300px";
testRootEl.appendChild(newNode);
var listView = setupListView(newNode, layoutName);
var tests = [
function () {
listView.ensureVisible(99);
return true;
},
function () {
listView.ensureVisible(0);
return true;
},
function () {
// Wait 1 second for the ARIA attributes to be set. Then we can find the
// grouping elements in the DOM by their role.
setTimeout(function () {
LiveUnit.Assert.areEqual(expectedNumberOfItemsInDom(),
listView._canvas.querySelectorAll(".win-item").length,
"Incorrect number of items in the DOM");
LiveUnit.Assert.areEqual(expectedNumberOfGroupsInDom(),
listView._canvas.querySelectorAll(".win-groupheader").length,
"Incorrect number of group headers in the DOM");
testRootEl.removeChild(newNode);
complete();
}, 1000);
},
];
Helper.ListView.runTests(listView, tests);
}
export class ReuseTests {
setUp() {
LiveUnit.LoggingCore.logComment("In setup");
testRootEl = document.createElement("div");
testRootEl.className = "file-listview-css";
var newNode = document.createElement("div");
newNode.id = "ReuseTests";
newNode.innerHTML =
"" +
"" +
""
testRootEl.appendChild(newNode);
document.body.appendChild(testRootEl);
newItems = 0;
disposedItemsCount = 0;
disposedItems = [];
}
tearDown() {
LiveUnit.LoggingCore.logComment("In tearDown");
WinJS.Utilities.disposeSubTree(testRootEl);
document.body.removeChild(testRootEl);
}
// Ensures dispose is called due to the following data source changes:
// - Item changed
// - Item removed
testDispose_GridLayout(complete) {
testDispose("GridLayout", complete);
}
testDomCleanupAfterScrolling_GridLayout(complete) {
domCleanupAfterScrollingTest("GridLayout", complete);
}
}
function generateChangeSelected(layoutName) {
ReuseTests.prototype["testChangeSelected" + layoutName] = function (complete) {
var items = [];
for (var i = 0; i < 100; i++) {
items.push({ title: "Tile" + i });
}
var list = new WinJS.Binding.List(items);
function renderer(itemPromise, recycled) {
return itemPromise.then(function (item) {
var element = recycled;
if (!element) {
element = document.createElement("div");
element.style.width = element.style.height = "100px";
}
element.textContent = item.data.title;
return element;
});
}
var newNode = document.createElement("div");
newNode.style.width = "600px";
newNode.style.height = "600px";
testRootEl.appendChild(newNode);
var listView = new WinJS.UI.ListView(newNode, {
itemDataSource: list.dataSource,
itemTemplate: renderer,
layout: new WinJS.UI[layoutName]()
});
listView.selection.set(0);
function checkTile(listview, index, text, selected) {
var tile = listview.elementFromIndex(index),
wrapper = tile.parentNode;
LiveUnit.Assert.areEqual(text, tile.textContent);
LiveUnit.Assert.areEqual(selected, WinJS.Utilities.hasClass(wrapper, WinJS.UI._selectedClass));
LiveUnit.Assert.areEqual(selected, tile.getAttribute("aria-selected") === "true");
LiveUnit.Assert.areEqual(selected, WinJS.Utilities._isSelectionRendered(wrapper));
}
var tests = [
function () {
checkTile(listView, 0, "Tile0", true);
checkTile(listView, 1, "Tile1", false);
list.setAt(0, { title: "Changed" });
return true;
},
function () {
checkTile(listView, 0, "Changed", true);
checkTile(listView, 1, "Tile1", false);
testRootEl.removeChild(newNode);
complete();
},
];
Helper.ListView.runTests(listView, tests);
};
};
generateChangeSelected("ListLayout");
function generateAriaCleanup(layoutName) {
ReuseTests.prototype["testAriaCleanup" + layoutName] = function (complete) {
var items = [];
for (var i = 0; i < 100; i++) {
items.push({ title: "Tile" + i });
}
var list = new WinJS.Binding.List(items);
function renderer(itemPromise, recycled) {
return itemPromise.then(function (item) {
var element = recycled;
if (!element) {
element = document.createElement("div");
element.style.width = element.style.height = "100px";
}
element.textContent = item.data.title;
return element;
});
}
var newNode = document.createElement("div");
newNode.style.width = "300px";
newNode.style.height = "300px";
testRootEl.appendChild(newNode);
var listView = new WinJS.UI.ListView(newNode, {
itemDataSource: list.dataSource,
itemTemplate: renderer
});
listView.selection.set([0, 1, 2, 3]);
Helper.ListView.waitForReady(listView)().then(function () {
LiveUnit.Assert.areEqual(4, newNode.querySelectorAll("[aria-selected='true']").length);
listView.ensureVisible(99);
return Helper.ListView.waitForDeferredAction(listView)();
}).then(function () {
LiveUnit.Assert.areEqual(0, newNode.querySelectorAll("[aria-selected='true']").length);
listView.ensureVisible(0);
return Helper.ListView.waitForDeferredAction(listView)();
}).then(function () {
LiveUnit.Assert.areEqual(4, newNode.querySelectorAll("[aria-selected='true']").length);
testRootEl.removeChild(newNode);
complete();
})
};
};
generateAriaCleanup("GridLayout");
}
// register the object as a test class by passing in the name
LiveUnit.registerTestClass("WinJSTests.ReuseTests");