// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License.txt in the project root for license information.
//
///
///
// Code shared by Items Manager unit tests
module Helper.ItemsManager {
"use strict";
var Scheduler = WinJS.Utilities.Scheduler;
// Override this on the WebUnit command line to desired multiplier
export var stressLevel = 1;
export function setTimeout(callback, delay) {
return window.setTimeout(LiveUnit.GetWrappedCallback(callback), delay);
};
export function setImmediate(callback) {
return WinJS.Utilities._setImmediate(LiveUnit.GetWrappedCallback(callback));
};
var seed = 0;
export function seedPseudorandom(n) {
seed = n;
};
export function pseudorandom(nMax) {
seed = (seed + 0.81282849124) * 2375.238208308;
seed -= Math.floor(seed);
return Math.floor(seed * nMax);
};
export function runStressTest(testOnce, repetitionMultiplier, signalTestCaseCompleted) {
var test = 0,
testMax = repetitionMultiplier * Helper.ItemsManager.stressLevel;
(function continueTest() {
LiveUnit.LoggingCore.logComment("Test " + test + " of " + testMax);
Helper.ItemsManager.seedPseudorandom(test);
testOnce(function () {
if (++test < testMax) {
WinJS.Utilities._setImmediate(continueTest);
} else {
signalTestCaseCompleted();
}
});
})();
}
export function simpleItem(index) {
return "Item" + index;
}
function simpleItemArray(count) {
var array = [];
for (var i = 0; i < count; ++i) {
array[i] = Helper.ItemsManager.simpleItem(i);
}
return array;
}
function bindingList(count) {
var list = new WinJS.Binding.List();
for (var i = 0; i < count; ++i) {
list.push(Helper.ItemsManager.simpleItem(i));
}
return list.dataSource;
}
function bindingListSimpleFilter(count) {
var list = new WinJS.Binding.List();
for (var i = 0; i < count; ++i) {
list.push(Helper.ItemsManager.simpleItem(i));
}
return list.createFiltered(function () { return true; }).dataSource;
}
export function simpleTestDataSource(controller, abilities, count) {
return Helper.ItemsManager.createTestDataSource(simpleItemArray(count), controller, abilities);
};
export var defaultLength = 80;
function listLength(length) {
return length === undefined ? Helper.ItemsManager.defaultLength : length;
}
export function testDataSourceWithDirectives(createTestDataSource) {
var directives = {
callMethodsSynchronously: false,
sendChangeNotifications: false,
countBeforeDelta: 0,
countAfterDelta: 0,
countBeforeOverride: -1,
countAfterOverride: -1,
returnIndices: false,
returnCount: false,
delay: null
};
var controller = {
directivesForMethod: function (method, args) {
return {
// Copy the current directives
callMethodSynchronously: directives.callMethodsSynchronously,
sendChangeNotifications: directives.sendChangeNotifications,
countBeforeDelta: directives.countBeforeDelta,
countAfterDelta: directives.countAfterDelta,
countBeforeOverride: directives.countBeforeOverride,
countAfterOverride: directives.countAfterOverride,
returnIndices: directives.returnIndices,
returnCount: directives.returnCount,
delay: directives.delay
};
}
};
// All abilities are enabled
var dataSource = createTestDataSource(controller);
dataSource.testDataAdapter.directives = directives;
return dataSource;
};
export function ensureAllAsynchronousRequestsFulfilled(dataSource) {
dataSource.testDataAdapter.directives.delay = 0;
}
export function simpleAsynchronousDataSource(length) {
return Helper.ItemsManager.testDataSourceWithDirectives(function (controller) {
// All abilities are enabled
return Helper.ItemsManager.simpleTestDataSource(controller, null, listLength(length));
});
};
export function simpleSynchronousArrayDataSource(array) {
var dataSource = Helper.ItemsManager.testDataSourceWithDirectives(function (controller) {
// All abilities are enabled
return Helper.ItemsManager.createTestDataSource(array, controller, null);
});
dataSource.testDataAdapter.directives.callMethodsSynchronously = true;
return dataSource;
};
export function defaultNotificationHandler() {
function assertUnexpected(methodName) {
LiveUnit.Assert.isTrue(false, 'Unexpected "' + methodName + '" notification received');
};
var countHasChanged = false,
countChangedBeginNotificationsReceived = false,
countChangedEndNotificationsReceived = false;
var notifications = [];
return {
dequeueNotification: function () {
return notifications.shift();
},
verifyExpectedNotifications: function (expectedMethods) {
LiveUnit.Assert.areEqual(expectedMethods.length, notifications.length, "Incorrect number of notifications was received");
while (notifications.length && expectedMethods.length) {
var notification = this.dequeueNotification();
var expectedMethod = expectedMethods.shift();
LiveUnit.Assert.areEqual(expectedMethod, notification.method, expectedMethod + " notification was not received when expected");
}
notifications = [];
},
// "Protected" method that should only be called by "derived" notification handlers
queueNotification: function (method, args) {
var argsObject;
switch (method) {
case "beginNotifications":
argsObject = {};
break;
case "inserted":
argsObject = {
item: args[0],
previous: args[1],
next: args[2]
};
break;
case "changed":
argsObject = {
newItem: args[0],
oldItem: args[1]
};
break;
case "moved":
argsObject = {
item: args[0],
previous: args[1],
next: args[2]
};
break;
case "removed":
argsObject = {
item: args[0],
mirage: args[1]
};
break;
case "countChanged":
argsObject = {
newCount: args[0],
oldCount: args[1]
};
break;
case "indexChanged":
argsObject = {
item: args[0],
newIndex: args[1],
oldIndex: args[2]
};
break;
case "endNotifications":
argsObject = {};
break;
}
notifications.push({
method: method,
args: argsObject
});
},
// The following methods should be overridden for notifications that are expected during a test
beginNotifications: function () {
// By default, tolerate the one beginNotifications notification caused by countChanged
if (countChangedBeginNotificationsReceived) {
assertUnexpected("endNotifications");
} else {
countChangedBeginNotificationsReceived = true;
}
},
inserted: function (item, previous, next) {
assertUnexpected("inserted");
},
changed: function (newItem, oldItem) {
assertUnexpected("changed");
},
moved: function (item, previous, next) {
assertUnexpected("moved");
},
removed: function (item, mirage) {
assertUnexpected("removed");
},
countChanged: function (newCount, oldCount) {
// By default, tolerate one countChanged notification
if (countHasChanged) {
assertUnexpected("countChanged");
} else {
countHasChanged = true;
}
},
indexChanged: function (item, newIndex, oldIndex) {
assertUnexpected("indexChanged");
},
endNotifications: function () {
// By default, tolerate the one endNotifications notification caused by countChanged
if (countChangedEndNotificationsReceived) {
assertUnexpected("endNotifications");
} else {
countChangedEndNotificationsReceived = true;
}
}
};
};
export function verifyRequestCount(testDataSource, expectedCount) {
LiveUnit.Assert.areEqual(expectedCount, testDataSource.testDataAdapter.requestCount(), "Unexpected number of outstanding requests");
};
export function defaultListNotificationHandler() {
function assertUnexpected(methodName) {
LiveUnit.Assert.isTrue(false, 'Unexpected "' + methodName + '" notification received');
};
var countHasChanged = false,
countChangedBeginNotificationsReceived = false,
countChangedEndNotificationsReceived = false;
var notifications = [];
return {
dequeueNotification: function () {
return notifications.shift();
},
clearNotifications: function () {
notifications = [];
},
verifyExpectedNotifications: function (expectedMethods) {
LiveUnit.Assert.areEqual(expectedMethods.length, notifications.length, "Incorrect number of notifications was received");
while (notifications.length && expectedMethods.length) {
var notification = this.dequeueNotification();
var expectedMethod = expectedMethods.shift();
LiveUnit.Assert.areEqual(expectedMethod, notification.method, expectedMethod + " notification was not received when expected");
}
notifications = [];
},
// "Protected" method that should only be called by "derived" notification handlers
queueNotification: function (method, args) {
var argsObject;
switch (method) {
case "beginNotifications":
argsObject = {};
break;
case "inserted":
argsObject = {
item: args[0],
previousHandle: args[1],
nextHandle: args[2]
};
break;
case "changed":
argsObject = {
newItem: args[0],
oldItem: args[1]
};
break;
case "moved":
argsObject = {
item: args[0],
previousHandle: args[1],
nextHandle: args[2]
};
break;
case "removed":
argsObject = {
handle: args[0],
mirage: args[1]
};
break;
case "countChanged":
argsObject = {
newCount: args[0],
oldCount: args[1]
};
break;
case "indexChanged":
argsObject = {
handle: args[0],
newIndex: args[1],
oldIndex: args[2]
};
break;
case "endNotifications":
argsObject = {};
break;
}
notifications.push({
method: method,
args: argsObject
});
},
// The following methods should be overridden for notifications that are expected during a test
beginNotifications: function () {
// By default, tolerate the one beginNotifications notification caused by countChanged
if (countChangedBeginNotificationsReceived) {
assertUnexpected("endNotifications");
} else {
countChangedBeginNotificationsReceived = true;
}
},
inserted: function (item, previousHandle, nextHandle) {
assertUnexpected("inserted");
},
changed: function (newItem, oldItem) {
assertUnexpected("changed");
},
moved: function (item, previousHandle, nextHandle) {
assertUnexpected("moved");
},
removed: function (handle, mirage) {
assertUnexpected("removed");
},
countChanged: function (newCount, oldCount) {
// By default, tolerate one countChanged notification
if (countHasChanged) {
assertUnexpected("countChanged");
} else {
countHasChanged = true;
}
},
indexChanged: function (handle, newIndex, oldIndex) {
assertUnexpected("indexChanged");
},
endNotifications: function () {
// By default, tolerate the one endNotifications notification caused by countChanged
if (countChangedEndNotificationsReceived) {
assertUnexpected("endNotifications");
} else {
countChangedEndNotificationsReceived = true;
}
},
};
};
export function setState(testDataSource, values) {
var items = [];
for (var i = 0, length = values.length; i < length; ++i) {
var value = values[i];
items.push({ key: value, data: Helper.ItemsManager.simpleItem(value) });
}
testDataSource.testDataAdapter.replaceItems(items);
};
function verifyItemIndex(item, index) {
if (typeof index === "number") {
LiveUnit.Assert.areEqual(index, item.index, "Item has incorrect index");
}
}
export function verifyItemData(item, index?) {
var data = item.data;
LiveUnit.Assert.isTrue(data !== undefined, "Item " + index + " data is undefined, key: " + item.key);
LiveUnit.Assert.isTrue(data !== null, "Item " + index + " data is null, key: " + item.key);
if (+index === index) {
LiveUnit.Assert.areEqual(Helper.ItemsManager.simpleItem(index), data, "Item " + index + " data has incorrect value");
verifyItemIndex(item, index);
}
};
export function simpleListNotificationHandler() {
var handler:any = Helper.ItemsManager.defaultListNotificationHandler();
var continuationNext;
handler.setContinuation = function (continuation) {
LiveUnit.Assert.areEqual(undefined, continuationNext, "Continuation set before previous one has executed");
continuationNext = continuation;
}
// Add methods to build up a list
var containerHead:any = {},
containerTail:any = {};
containerHead.next = containerTail;
containerTail.prev = containerHead;
var handleMap = {},
indexMap = [];
function insertContainer(handle, containerPrev, containerNext) {
LiveUnit.Assert.areEqual(undefined, handleMap[handle], "Item has already been inserted");
var container = {
prev: containerPrev,
next: containerNext
};
containerPrev.next = container;
containerNext.prev = container;
handleMap[handle] = container;
return container;
}
function removeContainer(handle) {
LiveUnit.Assert.isTrue(!!handleMap[handle], "Item is not in list");
var container = handleMap[handle];
container.prev.next = container.next;
container.next.prev = container.prev;
delete handleMap[handle];
}
function insertItem(itemPromise, containerPrev, containerNext) {
var container:any = insertContainer(itemPromise.handle, containerPrev, containerNext);
itemPromise.then(function (item) {
container.item = item;
});
}
handler.prependItemPromise = function (itemPromise) {
insertContainer(itemPromise.handle, containerHead, containerHead.next);
itemPromise.retain();
return itemPromise;
};
handler.appendItemPromise = function (itemPromise) {
insertContainer(itemPromise.handle, containerTail.prev, containerTail);
itemPromise.retain();
return itemPromise;
};
handler.storeItemPromise = function (itemPromise) {
LiveUnit.Assert.isTrue(typeof itemPromise.index === "number", "Item does not have a valid index");
LiveUnit.Assert.isTrue(!indexMap[itemPromise.index], "An item is already stored with the given index");
indexMap[itemPromise.index] = itemPromise;
itemPromise.retain();
};
handler.removeItemPromiseAtIndex = function (index) {
LiveUnit.Assert.isTrue(!!indexMap[index], "No item stored at the given index");
var itemPromise = indexMap[index];
delete indexMap[index];
itemPromise.release();
};
handler.updateItem = function (item) {
var container = handleMap[item.handle];
LiveUnit.Assert.isTrue(!!container, "Item is not in list");
LiveUnit.Assert.isTrue(!container.item, "Item is already available");
container.item = item;
};
// Add methods for verifying results
function verifyItemFetched(container, item) {
LiveUnit.Assert.isTrue(!!container.item, "Item has not been fetched");
};
handler.verifyItem = function (item, index) {
var container = handleMap[item.handle];
LiveUnit.Assert.isTrue(!!container, "Item has not been stored");
verifyItemFetched(container, item);
Helper.ItemsManager.verifyItemData(item, index);
};
handler.verifyState = function (values, testDataSource, nullKeysPossible) {
// Only verify the state of the data source if it's passed in
var items = testDataSource && testDataSource.testDataAdapter.getItems();
var container = containerHead.next;
for (var i = 0, len = values.length; i < len; i++) {
if (!nullKeysPossible || container.item.key) {
LiveUnit.Assert.areEqual(values[i].toString(), container.item.key, "Unexpected key");
}
if (items) {
if (!nullKeysPossible || container.item.key) {
LiveUnit.Assert.areEqual(items[i].key, container.item.key, "Received item key does not match that in data source");
}
LiveUnit.Assert.areEqual(items[i].data, container.item.data, "Received item data does not match that in data source");
}
container = container.next;
}
LiveUnit.Assert.areEqual(containerTail, container, "List does not end where expected");
};
// Override the notifications we expect in these tests
handler.beginNotifications = function () {
this.queueNotification("beginNotifications", arguments);
};
function previousContainer(previousHandle, nextHandle) {
var containerPrev, containerNext;
if (previousHandle && handleMap[previousHandle]) {
return handleMap[previousHandle];
} else if (nextHandle && handleMap[nextHandle]) {
return handleMap[nextHandle].prev;
} else if (!previousHandle && !nextHandle) {
LiveUnit.Assert.isTrue(containerHead.next === containerTail, "null, null notification for a non-empty list");
return containerHead;
} else {
return null;
}
}
handler.inserted = function (itemPromise, previousHandle, nextHandle) {
this.queueNotification("inserted", arguments);
itemPromise.retain();
var containerPrev = previousContainer(previousHandle, nextHandle);
insertItem(itemPromise, containerPrev, containerPrev.next);
};
handler.changed = function (newItem, oldItem) {
this.queueNotification("changed", arguments);
LiveUnit.Assert.areEqual(oldItem.handle, newItem.handle, "Changed handles do not match");
var container = handleMap[newItem.handle];
LiveUnit.Assert.isTrue(!!container, "Item is not in list");
LiveUnit.Assert.areEqual(oldItem, container.item, "Wrong item currently in list");
container.item = newItem;
};
handler.moved = function (itemPromise, previousHandle, nextHandle) {
this.queueNotification("moved", arguments);
if (handleMap[itemPromise.handle]) {
removeContainer(itemPromise.handle);
} else {
itemPromise.retain();
}
var containerPrev = previousContainer(previousHandle, nextHandle);
insertItem(itemPromise, containerPrev, containerPrev.next);
};
handler.removed = function (handle, mirage) {
this.queueNotification("removed", arguments);
if (handleMap[handle]) {
removeContainer(handle);
}
};
handler.countChanged = function (newCount, oldCount) {
this.queueNotification("countChanged", arguments);
};
handler.indexChanged = function (item, newIndex, oldIndex) {
this.queueNotification("indexChanged", arguments);
};
handler.endNotifications = function () {
this.queueNotification("endNotifications", arguments);
if (continuationNext) {
var cn = continuationNext;
Scheduler.schedule(function () {
cn();
}, Scheduler.Priority.high, null, "TestComponents.simpleListNotificationHandler._continuationNext");
continuationNext = undefined;
}
};
return handler;
};
export function stressListNotificationHandler() {
var handler:any = {};
var count;
var handleCounts = {},
handleTotalCounts = {},
handleToKeyMap = {},
keyToHandleMap = {};
var beginNotificationsReceived = false;
// For use in conditional breakpoints
var requests = 0,
results = 0;
// Maintain a simple, sparse linked list
function insertContainer(container, containerPrev, containerNext) {
LiveUnit.Assert.isTrue(!containerPrev || !containerPrev.next || containerPrev.next === containerNext, "Items are not adjacent");
LiveUnit.Assert.isTrue(!containerNext || !containerNext.prev || containerNext.prev === containerPrev, "Items are not adjacent");
container.prev = containerPrev;
container.next = containerNext;
if (containerPrev) {
containerPrev.next = container;
}
if (containerNext) {
containerNext.prev = container;
}
}
function removeContainer(container, mergeAdjacent) {
if (container.prev) {
container.prev.next = (mergeAdjacent ? container.next : null);
}
if (container.next) {
container.next.prev = (mergeAdjacent ? container.prev : null);
}
container.prev = null;
container.next = null;
}
// Add methods to retain and release items
var handleMap = {},
indexMap = [];
function containerFromHandle(handle) {
return handle ? handleMap[handle] : null;
}
function createContainer(itemPromise, containerPrev, containerNext) {
LiveUnit.Assert.isTrue(!!itemPromise.handle, "Null handle on itemPromise");
LiveUnit.Assert.areEqual(undefined, handleMap[itemPromise.handle], "Item has already been inserted");
var container = {
itemPromise: itemPromise
};
insertContainer(container, containerPrev, containerNext);
handleMap[itemPromise.handle] = container;
return container;
}
function destroyContainer(container, mergeAdjacent) {
var handle = container.itemPromise.handle;
LiveUnit.Assert.isTrue(!!handleMap[handle], "Item is not in list");
removeContainer(container, mergeAdjacent);
delete handleMap[handle];
}
handler.requestItem = function (itemPromise, index, handlePrev, handleNext) {
if (!itemPromise.handle) {
return;
}
requests++;
var handleCount = handleCounts[itemPromise.handle];
handleCounts[itemPromise.handle] = (handleCount ? handleCount + 1 : 1);
var handleTotalCount = handleTotalCounts[itemPromise.handle];
handleTotalCounts[itemPromise.handle] = (handleTotalCount ? handleTotalCount + 1 : 1);
if (itemPromise.index !== undefined && +index === index) {
LiveUnit.Assert.areEqual(itemPromise.index, index, "itemPromise has unexpected index");
}
if (+index !== index) {
index = itemPromise.index;
}
var containerPrev = containerFromHandle(handlePrev),
containerNext = containerFromHandle(handleNext);
var container = containerFromHandle(itemPromise.handle);
if (container) {
LiveUnit.Assert.isTrue(!containerPrev || !containerPrev.next || containerPrev.next === container, "Items are not adjacent");
LiveUnit.Assert.isTrue(!containerNext || !containerNext.prev || containerNext.prev === container, "Items are not adjacent");
if (container.prev) {
if (containerPrev) {
LiveUnit.Assert.isTrue(containerPrev === container.prev);
}
} else {
container.prev = containerPrev;
if (containerPrev) {
containerPrev.next = container;
}
}
if (container.next) {
if (containerNext) {
LiveUnit.Assert.isTrue(containerNext === container.next);
}
} else {
container.next = containerNext;
if (containerNext) {
containerNext.prev = container;
}
}
} else {
container = createContainer(itemPromise, containerPrev, containerNext);
if (itemPromise.index !== undefined) {
LiveUnit.Assert.isTrue(typeof itemPromise.index === "number", "itemPromise does not have a valid index");
LiveUnit.Assert.isTrue(beginNotificationsReceived || !indexMap[itemPromise.index], "itemPromise returned with index that is already in use");
indexMap[itemPromise.index] = container;
}
itemPromise.retain().then(function (item) {
if (item && !container.released) {
LiveUnit.Assert.isTrue(containerFromHandle(itemPromise.handle) === container, "Duplcate containers created");
LiveUnit.Assert.areEqual(itemPromise.handle, item.handle, "Handle for received item does not match that of itemPromise");
LiveUnit.Assert.isTrue(!handleToKeyMap[item.handle] || handleToKeyMap[item.handle] === item.key, "Handle has already been returned for different key");
handleToKeyMap[item.handle] = item.key;
LiveUnit.Assert.isTrue(!keyToHandleMap[item.key] || keyToHandleMap[item.key] === item.handle, "Different handle has already been returned for key");
keyToHandleMap[item.key] = item.handle;
container.item = item;
if (item.index !== undefined) {
LiveUnit.Assert.isTrue(typeof item.index === "number", "item does not have a valid index");
// In the midst of a batch of notifications, an inserted or moved promise can complete
// while the indexes are in a bogus state.
if (!beginNotificationsReceived) {
// itemPromise or indexChanged handler might have already added container to indexMap
LiveUnit.Assert.isTrue(!indexMap[item.index] || indexMap[item.index] === container, "itemPromise completed with index that is already in use");
}
indexMap[item.index] = container;
}
}
results++;
LiveUnit.Assert.isTrue(handleCounts[itemPromise.handle] > 0, "Received more items than requests for given handle");
if (handleCounts[itemPromise.handle] > 1) {
handleCounts[itemPromise.handle]--;
} else {
delete handleCounts[itemPromise.handle];
}
});
}
}
function releaseItem(container, mergeAdjacent?) {
if (container.item) {
delete handleToKeyMap[container.itemPromise.handle];
delete keyToHandleMap[container.item.key];
}
if (container.itemPromise.index !== undefined && indexMap[container.itemPromise.index] === container) {
delete indexMap[container.itemPromise.index];
}
container.itemPromise.release();
container.released = true;
destroyContainer(container, mergeAdjacent);
}
handler.releaseSomeItems = function () {
var keys = Object.keys(handleMap);
if (keys.length > 0) {
var handle = keys[Helper.ItemsManager.pseudorandom(keys.length)];
LiveUnit.Assert.isTrue(!!handle, "Invalid handle in map");
var container = containerFromHandle(handle),
i;
var releaseMax = 20;
var releaseBefore = Helper.ItemsManager.pseudorandom(releaseMax),
containerBefore = container.prev;
for (i = 0; containerBefore && i < releaseBefore; i++) {
var containerPrev = containerBefore.prev;
releaseItem(containerBefore);
containerBefore = containerPrev;
}
var releaseAfter = Helper.ItemsManager.pseudorandom(releaseMax),
containerAfter = container.next;
for (i = 0; containerAfter && i < releaseAfter; i++) {
var containerNext = containerAfter.next;
releaseItem(containerAfter);
containerAfter = containerNext;
}
releaseItem(container);
}
};
// Add methods for verifying results
handler.verifyIntermediateState = function () {
LiveUnit.Assert.isTrue(!beginNotificationsReceived, "endNotifications not received");
// Verify that internal data structures are in order and that all observations to date are consistent
// (i.e. indices are current, all containers retained).
for (var handle in handleMap) {
LiveUnit.Assert.isTrue(!!handle, "Invalid handle in map");
var container = handleMap[handle],
index = container.itemPromise.index;
if (index !== undefined) {
LiveUnit.Assert.isTrue(indexMap[index] === container, "Retained item index has changed without notification being sent");
}
if (container.prev) {
LiveUnit.Assert.isTrue(container.prev.next === container, "Container list malformed");
LiveUnit.Assert.isTrue(!!handleMap[container.prev.itemPromise.handle], "Previous item has not been retained properly");
if (index !== undefined && container.prev.index !== undefined) {
LiveUnit.Assert.areEqual(index - 1, container.prev.index, "Previous item does not have adjacent index");
}
}
if (container.next) {
LiveUnit.Assert.isTrue(container.next.prev === container, "Container list malformed");
LiveUnit.Assert.isTrue(!!handleMap[container.next.itemPromise.handle], "Next item has not been retained properly");
if (index !== undefined && container.next.index !== undefined) {
LiveUnit.Assert.areEqual(index + 1, container.next.index, "Next item does not have adjacent index");
}
}
}
};
function verifyItemFetched(container, item) {
LiveUnit.Assert.isTrue(!!container.item, "Item has not been fetched");
};
handler.verifyItem = function (item, index) {
LiveUnit.Assert.isTrue(!!item.handle, "Item has invalid handle");
var container = handleMap[item.handle];
LiveUnit.Assert.isTrue(!!container, "Item has not been stored");
verifyItemFetched(container, item);
Helper.ItemsManager.verifyItemData(item, index);
};
handler.verifyFinalState = function (itemArray) {
// Build a key map
var keyToIndexMap = {};
for (var i = 0, len = itemArray.length; i < len; i++) {
keyToIndexMap[itemArray[i].key] = i;
}
// Verify that all observations are true (i.e. "A is in the list", "A's data is blah", "A is before B")
for (var handle in handleMap) {
LiveUnit.Assert.isTrue(!!handle, "Invalid handle in map");
var container = handleMap[handle];
LiveUnit.Assert.isTrue(!!container.item, "Requested item still has not been fetched");
LiveUnit.Assert.isTrue(!!container.item.key, "Requested item has null key");
var index = keyToIndexMap[container.item.key];
LiveUnit.Assert.isTrue(+index === index, "Retained key no longer in actual list");
var item = itemArray[index];
LiveUnit.Assert.areEqual(item.data, container.item.data, "Data of retained and actual items does not match");
if (container.item.index !== undefined) {
LiveUnit.Assert.areEqual(index, container.item.index, "Retained item index does not match actual index");
LiveUnit.Assert.isTrue(indexMap[index] === container, "Retained item index has changed without notification being sent");
}
if (container.prev) {
LiveUnit.Assert.isTrue(!!handleMap[container.prev.itemPromise.handle], "Previous item has not been retained properly");
LiveUnit.Assert.areEqual(itemArray[index - 1].key, container.prev.item.key, "Retained previous item does not match actual previous item");
}
if (container.next) {
LiveUnit.Assert.isTrue(!!handleMap[container.next.itemPromise.handle], "Next item has not been retained properly");
LiveUnit.Assert.areEqual(itemArray[index + 1].key, container.next.item.key, "Retained next item does not match actual next item");
}
}
};
// Override the notifications we expect in these tests
handler.beginNotifications = function () {
LiveUnit.Assert.isFalse(beginNotificationsReceived, "Two beginNotifications received in a row");
beginNotificationsReceived = true;
};
handler.inserted = function (itemPromise, previousHandle, nextHandle) {
// Retain all items we are notified about (since we don't really learn anything by forgetting them)
handler.requestItem(itemPromise, null, previousHandle, nextHandle);
};
handler.changed = function (newItem, oldItem) {
LiveUnit.Assert.areEqual(oldItem.handle, newItem.handle, "Changed handles do not match");
LiveUnit.Assert.isTrue(!!oldItem.handle, "Invalid handle on item passed to changed handler");
var container = containerFromHandle(newItem.handle);
if (container) {
LiveUnit.Assert.areEqual(oldItem.handle, container.item.handle, "Wrong item currently in list");
container.item = newItem;
}
};
handler.moved = function (itemPromise, previousHandle, nextHandle) {
var container = containerFromHandle(itemPromise.handle);
if (container) {
removeContainer(container, true);
insertContainer(container, containerFromHandle(previousHandle), containerFromHandle(nextHandle));
} else {
// Retain all items we are notified about (since we don't really learn anything by forgetting them)
handler.requestItem(itemPromise, null, previousHandle, nextHandle);
}
};
handler.removed = function (handle, mirage) {
LiveUnit.Assert.isTrue(!!handle, "Invalid handle passed to removed handler");
var container = containerFromHandle(handle);
if (container) {
releaseItem(container, !mirage);
}
};
handler.countChanged = function (newCount, oldCount) {
for (var index in indexMap) {
if (indexMap[index].item) {
LiveUnit.Assert.isTrue(index < newCount, "Item with index larger than newCount still retained");
}
}
if (count !== undefined) {
LiveUnit.Assert.areEqual(oldCount, count, "oldCount does not equal most recent count");
}
count = newCount;
};
handler.indexChanged = function (handle, newIndex, oldIndex) {
LiveUnit.Assert.isTrue(!!handle, "Invalid handle passed to indexChanged handler");
var container = containerFromHandle(handle);
if (container) {
if (+oldIndex === oldIndex) {
var containerAtIndex = indexMap[oldIndex];
if (containerAtIndex && containerAtIndex.itemPromise.handle === handle) {
delete indexMap[oldIndex];
}
}
if (+newIndex === newIndex) {
indexMap[newIndex] = container;
}
}
};
handler.endNotifications = function () {
LiveUnit.Assert.isTrue(beginNotificationsReceived, "endNotifications received without matching beginNotifications");
beginNotificationsReceived = false;
};
return handler;
};
}