// 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 previousTracingOptions; var Promise = WinJS.Promise; function errorHandler(msg) { try { LiveUnit.Assert.fail('There was an unhandled error in your test: ' + msg); } catch (ex) { } } var SimpleDataAdapter = WinJS.Class.define(function (data) { this._data = data || []; }, { compareByIdentity: true, getCount: function () { return Promise.wrap(this._data.length); }, invalidateAll: function () { return this._notificationHandler.invalidateAll(); }, itemsFromIndex: function (requestIndex, countBefore, countAfter) { var data = this._data; var length = data.length; if (requestIndex >= length) { return Promise.wrap(new WinJS.ErrorFromName(WinJS.UI.FetchError.doesNotExist.toString())); } if (requestIndex < 0) { return Promise.wrap(new WinJS.ErrorFromName(WinJS.UI.FetchError.doesNotExist.toString())); } countBefore = Math.max(0, Math.min(requestIndex, countBefore)); countAfter = Math.max(0, Math.min((length - requestIndex) - 1, countAfter)); var response = { items: data.slice(requestIndex - countBefore, requestIndex + countAfter + 1), offset: countBefore, totalCount: length, absoluteIndex: requestIndex, atStart: countBefore === requestIndex }; return Promise.wrap(response); }, itemsFromKey: function (requestKey, countBefore, countAfter) { var data = this._data; for (var i = 0, len = data.length; i < len; i++) { if (requestKey === data[i].key) { return this.itemsFromIndex(i, countBefore, countAfter); } } return Promise.wrap(new WinJS.ErrorFromName(WinJS.UI.FetchError.doesNotExist.toString())); }, pop: function () { var data = this._data; var element = data.pop(); var index = data.length; this._notificationHandler.removed(element, index); return element; }, push: function (element) { var data = this._data; data.push(element); var index = data.length - 1; this._notificationHandler.inserted(element, index > 0 ? data[index - 1].key : null, null, index); }, removeAt: function (index) { var data = this._data; var element = data.splice(index, 1)[0]; this._notificationHandler.removed(element, index); return element; }, setNotificationHandler: function (notificationHandler) { // We need this to be able to trigger refresh this._notificationHandler = notificationHandler; } }); var SimpleDataSource = WinJS.Class.derive(WinJS.UI.VirtualizedDataSource, function (data) { this.adapter = new SimpleDataAdapter(data); this._baseDataSourceConstructor(this.adapter); }, { /* empty */ }); export class VirtualizedDataSourceTests { setUp() { previousTracingOptions = VDSLogging.options; VDSLogging.options = { log: function (message) { LiveUnit.Assert.fail(message); }, include: /createListBinding|_retainItem|_releaseItem|release/, handleTracking: true, logVDS: true, stackTraceLimit: 0 // set this to 100 to get good stack traces if you run into a failure. }; VDSLogging.on(); } tearDown() { VDSLogging.off(); VDSLogging.options = previousTracingOptions; } // Regression test for Win8:769372 // testInvalidateAllOnEmptyThenInsert(complete) { var sds = new SimpleDataSource(); var countChangedCount = 0; var insertedCount = 0; var expectedOldCount, expectedNewCount; var lb = sds.createListBinding({ countChanged: function (newCount, oldCount) { LiveUnit.Assert.areEqual(expectedOldCount, oldCount); LiveUnit.Assert.areEqual(expectedNewCount, newCount); countChangedCount++; }, inserted: function (itemPromise, previousHandle, nextHandle) { insertedCount++; } }); expectedOldCount = "unknown"; expectedNewCount = 0; sds.adapter.invalidateAll() Promise.timeout(100) .then(function () { // transition from 'unknown' -> 0 LiveUnit.Assert.areEqual(1, countChangedCount); LiveUnit.Assert.areEqual(0, insertedCount); expectedOldCount = 0; expectedNewCount = 1; sds.adapter.push({ key: "key1", data: "some data!" }); return Promise.timeout(100); }) .then(function () { // transition from 0 -> 1 LiveUnit.Assert.areEqual(2, countChangedCount); LiveUnit.Assert.areEqual(1, insertedCount); }) .then(null, errorHandler) .then(function () { lb.release(); }) .then(complete); } testInsertOnEmptyList(complete) { var sds = new SimpleDataSource(); var countChangedCount = 0; var insertedCount = 0; var lb = sds.createListBinding({ countChanged: function (newCount, oldCount) { countChangedCount++; }, inserted: function (itemPromise, previousHandle, nextHandle) { insertedCount++; } }); LiveUnit.Assert.areEqual(0, countChangedCount); LiveUnit.Assert.areEqual(0, insertedCount); sds.adapter.push({ key: "key1", data: "some data!" }); return Promise.timeout(100) .then(function () { // because it is still in an 'unknown' state we don't get a // countChanged for the insertion LiveUnit.Assert.areEqual(0, countChangedCount); LiveUnit.Assert.areEqual(1, insertedCount); }) .then(null, errorHandler) .then(function () { lb.release(); }) .then(complete); } xtestInsertOnEmptyListAfterCallingGetCount(complete) { var sds = new SimpleDataSource(); var countChangedCount = 0; var insertedCount = 0; var expectedOldCount, expectedNewCount; var lb = sds.createListBinding({ countChanged: function (newCount, oldCount) { LiveUnit.Assert.areEqual(expectedOldCount, oldCount); LiveUnit.Assert.areEqual(expectedNewCount, newCount); countChangedCount++; }, inserted: function (itemPromise, previousHandle, nextHandle) { insertedCount++; } }); LiveUnit.Assert.areEqual(0, countChangedCount); LiveUnit.Assert.areEqual(0, insertedCount); expectedOldCount = "unknown"; expectedNewCount = 0; sds.getCount().then(function (c) { // transition from 'unknown' -> 1 LiveUnit.Assert.areEqual(1, countChangedCount); LiveUnit.Assert.areEqual(0, insertedCount); expectedOldCount = 0; expectedNewCount = 1; sds.adapter.push({ key: "key1", data: "some data!" }); return Promise.timeout(100); }) .then(function () { // transition from 0 -> 1 LiveUnit.Assert.areEqual(2, countChangedCount); LiveUnit.Assert.areEqual(1, insertedCount); }) .then(null, errorHandler) .then(function () { lb.release(); }) .then(complete); } // Regression test for Win8:630529 // testRemoveCurrentListBindingElement(complete) { var sds = new SimpleDataSource(); var lb = sds.createListBinding(); sds.adapter.push({ key: "key1", data: "data1" }); sds.adapter.push({ key: "key2", data: "data2" }); sds.adapter.push({ key: "key3", data: "data3" }); sds.adapter.push({ key: "key4", data: "data4" }); return Promise.timeout(100) .then(function () { return lb.first(); }) .then(function (item) { LiveUnit.Assert.areEqual("key1", item.key); LiveUnit.Assert.areEqual("data1", item.data); return lb.next(); }) .then(function (item) { LiveUnit.Assert.areEqual("key2", item.key); LiveUnit.Assert.areEqual("data2", item.data); var removed = sds.adapter.removeAt(1); LiveUnit.Assert.areEqual("key2", removed.key); LiveUnit.Assert.areEqual("data2", removed.data); return Promise.timeout(100); }) .then(function () { return lb.current(); }) .then(function (item) { LiveUnit.Assert.areEqual("key3", item.key); LiveUnit.Assert.areEqual("data3", item.data); return lb.previous(); }) .then(function (item) { LiveUnit.Assert.areEqual("key1", item.key); LiveUnit.Assert.areEqual("data1", item.data); return lb.first(); }) .then(function (item) { LiveUnit.Assert.areEqual("key1", item.key); LiveUnit.Assert.areEqual("data1", item.data); }) .then(null, errorHandler) .then(function () { lb.release(); }) .then(complete); } testDoubleRelease(complete) { VDSLogging.options.log = function (message) { throw new WinJS.ErrorFromName("VDSLogging.ASSERT", message); }; var sds = new SimpleDataSource(); var countChangedCount = 0; var insertedCount = 0; var lb = sds.createListBinding({ countChanged: function (newCount, oldCount) { countChangedCount++; }, inserted: function (itemPromise, previousHandle, nextHandle) { insertedCount++; } }); LiveUnit.Assert.areEqual(0, countChangedCount); LiveUnit.Assert.areEqual(0, insertedCount); sds.adapter.push({ key: "key1", data: "some data!" }); var first = lb.first(); first.retain(); return Promise.timeout(100) .then(function () { // because it is still in an 'unknown' state we don't get a // countChanged for the insertion LiveUnit.Assert.areEqual(0, countChangedCount); LiveUnit.Assert.areEqual(1, insertedCount); first.release(); try { first.release(); LiveUnit.Assert.fail("Should not get here"); } catch (e) { LiveUnit.Assert.areEqual("VDSLogging.ASSERT", e.name); } }) .then(null, errorHandler) .then(function () { lb.release(); }) .then(complete); } // Regression test for Win8:847191 // testInsertsOnNonEmptyListBeforeFirstRequest(complete) { var range = function (min, max) { var a = []; for (; min < max; min++) { a.push(min); } return a; }; var ds = new SimpleDataSource(range(1, 101).map(function (el) { return { key: "" + el, data: el } })); var insertedCount = 0; var indexChangedCount = 0; var binding = ds.createListBinding({ inserted: function (itemPromise) { itemPromise.retain(); insertedCount++; }, indexChanged: function (handle, newIndex, oldIndex) { indexChangedCount++; } }); ds.adapter._notificationHandler.beginNotifications(); range(200, 300).forEach(function (el) { ds.adapter.push({ key: "" + el, data: el }); }); ds.adapter._notificationHandler.endNotifications(); ds.getCount() .then(function (c) { LiveUnit.Assert.areEqual(0, indexChangedCount); LiveUnit.Assert.areEqual(0, insertedCount); LiveUnit.Assert.areEqual(200, c); }) .then(null, errorHandler) .then(complete); } // Regression test for WinBlue:22439 // testInsertsWithNoIndexAndAKeyThatIsNotBeingTracked(complete) { VDSLogging.options.log = function (message) { throw new WinJS.ErrorFromName("VDSLoggin.g.ASSERT", message); }; var sds = new SimpleDataSource(); var countChangedCount = 0; var insertedCount = 0; var newCountValue = 0; var lb = sds.createListBinding({ countChanged: function (newCount, oldCount) { countChangedCount++; newCountValue = newCount; }, inserted: function (itemPromise, previousHandle, nextHandle) { insertedCount++; } }); LiveUnit.Assert.areEqual(0, countChangedCount); LiveUnit.Assert.areEqual(0, insertedCount); sds.adapter.push({ key: "key4", data: "data4" }); sds.adapter.push({ key: "key5", data: "data5" }); sds.adapter.push({ key: "key6", data: "data6" }); return Promise.timeout(100) .then(function () { LiveUnit.Assert.areEqual(0, countChangedCount); LiveUnit.Assert.areEqual(1, insertedCount); return Promise.timeout(100); }) .then(function () { var item1 = { key: "key2", data: "data2" }; var item2 = { key: "key3", data: "data3" }; sds.adapter._data.unshift(item2); sds.adapter._data.unshift(item1); // Notify an insert passing previousKey = key2, which VDS is currently not tracking. This will // make it start a refresh, and we should eventually end up in a good state with a valid // count = 5 // sds.adapter._notificationHandler.inserted(item2, "key2", null); return Promise.timeout(100); }) .then(function () { LiveUnit.Assert.areEqual(1, countChangedCount); LiveUnit.Assert.areEqual(newCountValue, 5); return Promise.timeout(100); }) .then(null, errorHandler) .then(function () { lb.release(); }) .then(complete); } // Verifies that when the VDS cancels the data adapter's getCount promise and the data // adapter eats the cancelation by returning 0, the VDS doesn't get stuck in a state // in which it believes that the data source is empty. // Regression test for WinBlue#304358 // testDataAdapterEatingCountCancelation(complete) { var EatingDataAdapter = WinJS.Class.derive(SimpleDataAdapter, function () { SimpleDataAdapter.apply(this, arguments); }, { getCount: function () { return Promise.timeout().then(function () { return this._data.length; }, function (error) { // Eats the cancelation and returns 0. // return 0; }); } }); var EatingDataSource = WinJS.Class.derive(WinJS.UI.VirtualizedDataSource, function (data) { this.adapter = new EatingDataAdapter(data); this._baseDataSourceConstructor(this.adapter); }, { /* empty */ }); var data = [ { key: "key0", data: "Item 0" }, { key: "key1", data: "Item 1" }, { key: "key2", data: "Item 2" } ]; var ds = new EatingDataSource(data); ds.getCount().cancel(); ds.itemFromIndex(0).then(function (item) { LiveUnit.Assert.areEqual("Item 0", item.data); complete(); }); } }; } // Register the object as a test class by passing in the name LiveUnit.registerTestClass("WinJSTests.VirtualizedDataSourceTests");