| /// <reference path="../src/datajs.js" /> |
| /// <reference path="../src/odata-utils.js" /> |
| /// <reference path="../src/cache.js" /> |
| /// <reference path="common/djstest.js" /> |
| |
| // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. |
| // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation |
| // files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, |
| // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the |
| // Software is furnished to do so, subject to the following conditions: |
| // |
| // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. |
| // |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE |
| // WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR |
| // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, |
| // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| |
| // cache-tests.js |
| |
| (function (window, undefined) { |
| |
| module("Unit"); |
| var foodsFeed = "./endpoints/FoodStoreDataServiceV4.svc/Foods"; |
| var collectionSize = 16; |
| |
| var thenFailTest = function (err) { |
| if (err && err.message) { |
| djstest.fail(err.message); |
| } else { |
| djstest.fail("unexepected promise failure"); |
| } |
| |
| djstest.done(); |
| }; |
| |
| djstest.addTest(function dataCacheCountTest() { |
| var cache = odatajs.createDataCache({ name: "cache", source: foodsFeed }); |
| cache.count().then(function (count) { |
| djstest.assertAreEqual(count, collectionSize, "expected count for Foods"); |
| djstest.destroyCacheAndDone(cache); |
| }, thenFailTest); |
| }); |
| |
| djstest.addTest(function dataCacheCountOnLocalTest() { |
| var cache = odatajs.createDataCache({ name: "cache", source: foodsFeed, pageSize: collectionSize + 10, mechanism: "memory" }); |
| cache.readRange(0, collectionSize + 10).then(function (data) { |
| var expectedCount = data.value ? data.value.length : 0; |
| cache.count().then(function (count) { |
| djstest.assertAreEqual(count, expectedCount, "expected count for expectedCount"); |
| djstest.destroyCacheAndDone(cache); |
| }, thenFailTest); |
| }, thenFailTest); |
| }); |
| |
| djstest.addTest(function dataCacheCountAbortTest() { |
| // Abort before completion. |
| var cache = odatajs.createDataCache({ name: "cache", source: foodsFeed }); |
| var item = cache.count().then(thenFailTest, function (err) { |
| djstest.assertAreEqual(true, err.canceled, "err.aborted is true"); |
| djstest.destroyCacheAndDone(cache); |
| }).cancel(); |
| }); |
| |
| var createNewCache = function (options) { |
| |
| var thenSuccess = null; |
| |
| var resolved = false; |
| var rejected = false; |
| |
| var args = null; |
| |
| this.then = function (success) { |
| if (resolved) { |
| success.apply(null, args); |
| } else if (!rejected) { |
| thenSuccess = success; |
| } |
| }; |
| |
| var resolve = function (/*args*/) { |
| resolved = true; |
| args = arguments; |
| if (thenSuccess) { |
| thenSuccess.apply(null, arguments); |
| } |
| }; |
| |
| var cache = odatajs.createDataCache(options); |
| cache.clear().then(function () { |
| var newCache = odatajs.createDataCache(options); |
| resolve(newCache); |
| }, function (err) { |
| rejected = true; |
| thenFailTest(err); |
| }); |
| |
| return this; |
| }; |
| |
| djstest.addTest(function dataCacheReadRangeSingleTest() { |
| // Read a new range. |
| var options = { name: "cache", source: foodsFeed, pageSize: 2 }; |
| createNewCache(options). |
| then(function (cache) { |
| cache.readRange(0, 1). |
| then(function (data) { |
| djstest.assertAreEqual(data.value.length, 1, "single item was read."); |
| djstest.assertAreEqual(data.value[0].FoodID, 0, "food id is 0."); |
| djstest.done(); |
| }, thenFailTest); |
| }, thenFailTest); |
| }); |
| |
| djstest.addTest(function dataCacheReadRangeExactPageTest() { |
| // Read exactly one page. |
| var options = { name: "cache", source: foodsFeed, pageSize: 2 }; |
| createNewCache(options). |
| then(function (cache) { |
| cache.readRange(0, 2). |
| then(function (data) { |
| djstest.assertAreEqual(data.value.length, 2, "single item was read."); |
| djstest.assertAreEqual(data.value[0].FoodID, 0, "first food id is 0."); |
| djstest.assertAreEqual(data.value[1].FoodID, 1, "second food id is 1."); |
| djstest.done(); |
| }, thenFailTest); |
| }, thenFailTest); |
| }); |
| |
| djstest.addTest(function dataCacheReadRangeMultiplePageTest() { |
| // Read multiple pages. |
| var options = { name: "cache", source: foodsFeed, pageSize: 2 }; |
| createNewCache(options). |
| then(function (cache) { |
| cache.readRange(0, 3). |
| then(function (data) { |
| djstest.assertAreEqual(data.value.length, 3, "single item was read."); |
| djstest.assertAreEqual(data.value[0].FoodID, 0, "first food id is 0."); |
| djstest.assertAreEqual(data.value[1].FoodID, 1, "second food id is 1."); |
| djstest.assertAreEqual(data.value[2].FoodID, 2, "third food id is 2."); |
| djstest.done(); |
| }, thenFailTest); |
| }, thenFailTest); |
| }); |
| |
| djstest.addTest(function dataCacheReadRangeSyncDelaysTest() { |
| var options = { name: "cache", source: foodsFeed, pageSize: 2 }; |
| var counter = 0; |
| createNewCache(options). |
| then(function (cache) { |
| cache.readRange(0, 1). |
| then(function (data) { |
| djstest.assertAreEqual(counter, 0, "counter is zero for first set of results"); |
| djstest.assertAreEqual(cache.stats.netReads, 1, "one request made to fulfill the readRange"); |
| counter++; |
| cache.readRange(0, 1). |
| then(function (data) { |
| djstest.assertAreEqual(counter, 2, "counter is two because even sync results are delayed)"); |
| djstest.assertAreEqual(cache.stats.netReads, 1, "no additional requests since requested data is in cache"); |
| djstest.done(); |
| }, thenFailTest); |
| djstest.assertAreEqual(counter, 1, "counter is one because readRange hasn't run (even if results are cached)"); |
| counter++; |
| }, thenFailTest); |
| }, thenFailTest); |
| }); |
| |
| djstest.addTest(function dataCacheReadRangesWithDestroyTest() { |
| var options = { name: "cache", source: foodsFeed, pageSize: 2, prefetchSize: 0 }; |
| var counter = 0; |
| createNewCache(options).then(function (cache) { |
| cache.readRange(0, 1).then(function (data) { |
| djstest.assertAreEqual(cache.stats.netReads, 1, "one request made to fulfill the readRange"); |
| cache.clear().then(function () { |
| djstest.assertAreEqual(cache.stats.netReads, 0, "stats cleared in destroy"); |
| cache.readRange(0, 1).then(function (data) { |
| djstest.assertAreEqual(cache.stats.netReads, 1, "request made after destroy to fulfill the readRange"); |
| djstest.done(); |
| }, thenFailTest); |
| }, thenFailTest); |
| }, thenFailTest); |
| }, thenFailTest); |
| }); |
| |
| djstest.addTest(function dataCacheReadSimultaneousTest() { |
| var options = { name: "cache", source: foodsFeed, pageSize: 2 }; |
| var counter = 0; |
| var theCache; |
| var checkDataAndCount = function (data) { |
| djstest.assertAreEqual(data.value.length, 1, "Single item returned"); |
| djstest.assertAreEqual(data.value[0].FoodID, 0, "FoodId is set to zero for first item"); |
| djstest.assertAreEqual(theCache.stats.netReads, 1, "single theCache.stats.netReads"); |
| djstest.assert(theCache.stats.prefetches <= 1, "theCache.stats.prefetches <= 1 - no repetitions"); |
| counter++; |
| if (counter === 3) { |
| djstest.done(); |
| } |
| }; |
| |
| createNewCache(options). |
| then(function (cache) { |
| theCache = cache; |
| cache.readRange(0, 1).then(checkDataAndCount, thenFailTest); |
| cache.readRange(0, 1).then(checkDataAndCount, thenFailTest); |
| cache.readRange(0, 1).then(checkDataAndCount, thenFailTest); |
| }, thenFailTest); |
| }); |
| |
| djstest.addTest(function dataCacheMultipleClearTest() { |
| var options = { name: "cache", source: foodsFeed, pageSize: 2 }; |
| var counter = 0; |
| var checkCount = function (data) { |
| djstest.assert(true, "clear then was called"); |
| counter++; |
| if (counter === 3) { |
| djstest.done(); |
| } |
| }; |
| |
| createNewCache(options). |
| then(function (cache) { |
| cache.readRange(0, 1).then(function () { |
| cache.clear().then(checkCount, thenFailTest); |
| cache.clear().then(checkCount, thenFailTest); |
| cache.clear().then(checkCount, thenFailTest); |
| }, thenFailTest); |
| }, thenFailTest); |
| }); |
| |
| djstest.addTest(function dataCacheOnIdleIsFired() { |
| var options = { name: "cache", source: foodsFeed, pageSize: 2, prefetchSize: 0 }; |
| |
| createNewCache(options). |
| then(function (cache) { |
| var counter = 0; |
| var clearSucceeded = false; |
| |
| var thenFailThisTest = function (err) { |
| if (err && err.message) { |
| djstest.fail(err.message); |
| } else { |
| djstest.fail("unexepected promise failure"); |
| } |
| }; |
| |
| cache.onidle = function () { |
| counter++; |
| djstest.assertAreEqual(counter, 1, "onidle was called 1 times"); |
| djstest.assert(clearSucceeded, "onidle was called after destroy"); |
| djstest.done(); |
| }; |
| |
| cache.readRange(0, 1).then(null, thenFailThisTest); |
| cache.readRange(0, 1).then(null, thenFailThisTest); |
| cache.readRange(3, 4).then(function () { |
| cache.readRange(5, 6).then(function () { |
| cache.clear().then(function () { |
| clearSucceeded = true; |
| }, thenFailThisTest); |
| }, thenFailThisTest); |
| }, thenFailThisTest); |
| }, thenFailTest); |
| }); |
| |
| djstest.addTest(function dataCacheOnIdleFiresOnErrorTest() { |
| |
| var errorResponse = false; |
| var httpClient = { |
| request: function (request, success, error) { |
| var response = { statusCode: 200, headers: { "Content-Type": "application/json", "OData-Version": "4.0" }, body: JSON.stringify({ d: [1, 2] }) }; |
| if (!errorResponse) { |
| errorResponse = true; |
| setTimeout(function () { |
| success(response); |
| }, 0); |
| } else { |
| response.statusCode = 500; |
| response.body = "bad response"; |
| setTimeout(function () { |
| error({ message: "HTTP request failed", request: request, response: response }); |
| }, 0); |
| } |
| } |
| }; |
| |
| var options = { name: "cache", source: foodsFeed, pageSize: 2, prefetchSize: 0, httpClient: httpClient }; |
| |
| createNewCache(options). |
| then(function (cache) { |
| var counter = 0; |
| var errorHandlerCalled = false; |
| |
| var thenFailThisTest = function (err) { |
| if (err && err.message) { |
| if (errorResponse) { |
| djstest.assertAreEqual(err.message, "HTTP request failed", "Error is the expected one"); |
| errorHandlerCalled = true; |
| } else { |
| djstest.fail(err.message); |
| } |
| } else { |
| djstest.fail("unexepected promise failure"); |
| } |
| }; |
| |
| cache.onidle = function () { |
| counter++; |
| djstest.assertAreEqual(counter, 1, "onidle was called"); |
| djstest.assert(errorHandlerCalled, "error handler was called before onidle"); |
| cache.onidle = null; |
| cache.clear().then(function () { |
| djstest.done(); |
| }, thenFailTest); |
| }; |
| cache.readRange(0, 2).then(function () { |
| cache.readRange(2, 4).then(function () { |
| djstest.fail("unexpected readRange success"); |
| }, thenFailThisTest); |
| }, thenFailThisTest); |
| }, thenFailTest); |
| }); |
| |
| |
| djstest.addTest(function dataCacheOdataSourceNormalizedURITest() { |
| var requestsMade = 0; |
| var httpClient = { |
| request: function (request, success, error) { |
| var response = { statusCode: 200, headers: { "Content-Type": "application/json", "OData-Version": "4.0" }, body: JSON.stringify({ value: [1, 2] }) }; |
| if (requestsMade === 0) { |
| djstest.pass("Cache made first request for data from the source"); |
| requestsMade++; |
| } else { |
| // In memory storage will make a second request from the new cache since two caches with the same name will coexist |
| if (window.mozIndexedDB || window.localStorage || requestsMade > 1) { |
| djstest.fail("Unexpected request to the source"); |
| } else { |
| djstest.pass("In memory cache requested the data from the source"); |
| } |
| } |
| setTimeout(function () { |
| success(response); |
| }, 0); |
| } |
| }; |
| |
| var options = { name: "cache", source: "http://exampleuri.com/my service.svc", pageSize: 2, prefetchSize: 0, httpClient: httpClient }; |
| |
| createNewCache(options). |
| then(function (cache) { |
| cache.readRange(0, 2).then(function () { |
| options.source = "HtTp://ExampleURI.cOm/my%20service.svc"; |
| var newCache = odatajs.createDataCache(options); |
| newCache.readRange(0, 2).then(function (data) { |
| djstest.assertAreEqualDeep(data.value, [1, 2], "Got the expected data from the new cache instance"); |
| newCache.clear().then(function () { |
| djstest.done(); |
| }, thenFailTest); |
| }, thenFailTest); |
| }, thenFailTest); |
| }, thenFailTest); |
| }); |
| |
| |
| djstest.addTest(function dataCachePrefetchAllTest() { |
| var options = { name: "cache", source: foodsFeed, pageSize: 2, prefetchSize: -1 }; |
| var counter = 0; |
| var theCache; |
| |
| var callback = function () { |
| counter++; |
| if (counter === 2) { |
| djstest.assertAreEqual(1, theCache.stats.netReads, "single page to satisfy read (" + theCache.stats.netReads + ")"); |
| djstest.assert(theCache.stats.prefetches > 1, "theCache.stats.prefetches(" + theCache.stats.prefetches + ") > 1 - multiple prefetches"); |
| djstest.done(); |
| } |
| }; |
| |
| var checkDataAndCount = function (data) { |
| djstest.assertAreEqual(data.value.length, 1, "Single item returned"); |
| djstest.assertAreEqual(data.value[0].FoodID, 0, "FoodId is set to zero for first item"); |
| djstest.assertAreEqual(1, theCache.stats.netReads, "single theCache.stats.netReads"); |
| djstest.assert(theCache.stats.prefetches <= 1, "theCache.stats.prefetches <= 1 - no repetitions"); |
| callback(); |
| }; |
| |
| createNewCache(options). |
| then(function (cache) { |
| theCache = cache; |
| cache.readRange(0, 1).then(checkDataAndCount, thenFailTest); |
| cache.onidle = function () { |
| djstest.log("onidle fired"); |
| callback(); |
| }; |
| }, thenFailTest); |
| }); |
| |
| djstest.addTest(function dataCacheReadRangeTakeMoreThanCollectionCountTest() { |
| // Read multiple pages. |
| var options = { name: "cache", source: foodsFeed, pageSize: 2 }; |
| createNewCache(options). |
| then(function (cache) { |
| cache.count().then(function (count) { |
| cache.readRange(0, count + 1). |
| then(function (data) { |
| djstest.assertAreEqual(data.value.length, count, "all items in the collection were read."); |
| cache.readRange(2, count + 1). |
| then(function (data) { |
| djstest.assertAreEqual(data.value.length, count - 2, "all requested in the collection were read."); |
| djstest.assertAreEqual(data.value[0].FoodID, 2, "first read food id is 2."); |
| djstest.done(); |
| }, thenFailTest); |
| }, thenFailTest); |
| }, thenFailTest); |
| }, thenFailTest); |
| }); |
| |
| djstest.addTest(function dataCacheReadRangeSkipMoreThanCollectionCountTest() { |
| // Read multiple pages. |
| var options = { name: "cache", source: foodsFeed, pageSize: 2 }; |
| createNewCache(options). |
| then(function (cache) { |
| cache.count().then(function (count) { |
| cache.readRange(count + 1, 5). |
| then(function (data) { |
| djstest.assertAreEqual(data.value.length, 0, "no items were read."); |
| djstest.done(); |
| }, thenFailTest); |
| }, thenFailTest); |
| }, thenFailTest); |
| }); |
| |
| djstest.addTest(function dataCacheReadRangeTakeMoreThanPrefetchSizeTest() { |
| // Read multiple pages. |
| var options = { name: "cache", source: foodsFeed, pageSize: 2, prefetchSize: 1 }; |
| createNewCache(options). |
| then(function (cache) { |
| cache.readRange(0, 4). |
| then(function (data) { |
| djstest.assertAreEqual(data.value.length, 4, "all requested in the collection were read."); |
| djstest.assertAreEqual(data.value[0].FoodID, 0, "first food id is 0."); |
| djstest.assertAreEqual(data.value[1].FoodID, 1, "second food id is 1."); |
| djstest.assertAreEqual(data.value[2].FoodID, 2, "third food id is 2."); |
| djstest.assertAreEqual(data.value[3].FoodID, 3, "third food id is 3."); |
| djstest.done(); |
| }, thenFailTest); |
| }, thenFailTest); |
| }); |
| |
| djstest.addTest(function dataCacheRangeInvalidIndexAndCount() { |
| var options = { name: "cache", source: foodsFeed, pageSize: 2, prefetchSize: 1 }; |
| var counter = 0; |
| |
| var thenFailSuccess = function () { |
| djstest.fail("Call to success was unexpected"); |
| counter++; |
| if (counter === tests.length) { |
| djstest.done(); |
| } |
| }; |
| |
| var thenFailError = function () { |
| djstest.fail("Call to error was unexpected"); |
| counter++; |
| if (counter === tests.length) { |
| djstest.done(); |
| } |
| }; |
| |
| var invalidValues = [-5, -1, null, undefined, NaN, Infinity, "5", "this is not a number"]; |
| var tests = []; |
| $.each(invalidValues, function (_, value) { |
| tests.push({ i: value, c: 0 }); |
| tests.push({ i: 0, c: value }); |
| tests.push({ i: value, c: value }); |
| }); |
| |
| createNewCache(options). |
| then(function (cache) { |
| var i, len; |
| for (i = 0, len = tests.length; i < len; i++) { |
| var test = tests[i]; |
| try { |
| cache.readRange(test.i, test.c).then(thenFailSuccess, thenFailTest); |
| } catch (err) { |
| djstest.pass("Expected exception was thrown: " + err.message); |
| counter++; |
| } |
| } |
| if (counter === tests.length) { |
| djstest.done(); |
| } |
| }); |
| }); |
| |
| |
| djstest.addTest(function cacheOptionsForCountTest() { |
| var httpClient = { |
| request: function (r, success, error) { |
| window.setTimeout(function () { |
| success({ data: "10" }); |
| }, 1); |
| return null; |
| } |
| }; |
| var cache = odatajs.createDataCache({ |
| name: "mem", mechanism: "memory", source: "http://www.example.org/service/", |
| httpClient: httpClient |
| }); |
| cache.count().then(function (count) { |
| djstest.assertAreEqual(count, 10, "count value"); |
| djstest.done(); |
| }, djstest.failAndDoneCallback("failed to get count")); |
| }); |
| |
| djstest.addTest(function dataCacheDestoryStopsThePrefetcherTest() { |
| var oldHttpClientRequest = OData.defaultHttpClient.request; |
| var prefetchCount = 0; |
| var theCache; |
| |
| OData.defaultHttpClient.request = function (request, success, error) { |
| prefetchCount++; |
| djstest.assert(prefetchCount <= 3, "Expected prefetch request"); |
| if (prefetchCount === 3) { |
| theCache.clear().then(function () { |
| djstest.assertAreEqual(prefetchCount, 3, "cache.clear() stopped the prefetcher"); |
| djstest.done(); |
| OData.defaultHttpClient.request = oldHttpClientRequest; |
| }, thenFailTest); |
| return { |
| abort: function () { } |
| }; |
| } |
| return oldHttpClientRequest(request, success, error); |
| }; |
| |
| try { |
| var options = { name: "cache", source: foodsFeed, pageSize: 1, prefetchSize: -1 }; |
| createNewCache(options).then(function (cache) { |
| theCache = cache; |
| cache.readRange(0, 0).then(null, thenFailTest); |
| }); |
| } catch (e) { |
| OData.defaultHttpClient.request = oldHttpClientRequest; |
| djstest.fail("Exception thrown, prefetchSize: " + tests[count] + " error: " + e.message); |
| djstest.done(); |
| } |
| }); |
| |
| djstest.addTest(function dataCacheFilterTest() { |
| var options = { name: "cache", source: foodsFeed, pageSize: 3, prefetchSize: -1 }; |
| var counter = 0; |
| |
| var singleItemPredicate = function (data) { |
| return data.FoodID === 2; |
| }; |
| |
| var multipleItemPredicate = function (data) { |
| return data.FoodID % 2 === 1; |
| }; |
| |
| var noItemPredicate = function (data) { |
| return data.Name === "something i would never eat"; |
| }; |
| |
| var allItemPredicate = function (data) { |
| return data.FoodID >= 0; |
| }; |
| |
| var doneAfterAllTests = function () { |
| counter++; |
| if (counter === tests.length) { |
| djstest.done(); |
| } |
| }; |
| |
| var last = collectionSize - 1; |
| var tests = [ |
| { index: 0, count: -5, predicate: singleItemPredicate }, // Single match in entire collection |
| {index: 2, count: 1, predicate: singleItemPredicate }, // Single match, single count |
| {index: 3, count: 1, predicate: singleItemPredicate }, // Single match skipped, i.e. no matches |
| {index: 0, count: -1, predicate: multipleItemPredicate }, // Multiple matches in entire collection |
| {index: 0, count: 5, predicate: multipleItemPredicate }, // Multiple matches, take partial |
| {index: 3, count: 5, predicate: multipleItemPredicate }, // Multiple matches, skip/take partial |
| {index: 7, count: 10, predicate: multipleItemPredicate }, // Multiple matches, skip partial, take past end of collection |
| {index: 13, count: 4, predicate: allItemPredicate }, // All items match, skip/take partial |
| {index: 0, count: 20, predicate: noItemPredicate }, // No matches |
| {index: 0, count: 0, predicate: allItemPredicate }, // Zero count |
| {index: -5, count: 1, predicate: allItemPredicate }, // Negative index |
| {index: last + 1, count: 1, predicate: allItemPredicate }, // Index past end of collection |
| |
| {index: last, count: -5, predicate: singleItemPredicate, backwards: true }, // Single match in entire collection |
| {index: 2, count: 1, predicate: singleItemPredicate, backwards: true }, // Single match, single count |
| {index: 1, count: 1, predicate: singleItemPredicate, backwards: true }, // Single match skipped, i.e. no matches |
| {index: last, count: -1, predicate: multipleItemPredicate, backwards: true }, // Multiple matches in entire collection |
| {index: last, count: 6, predicate: multipleItemPredicate, backwards: true }, // Multiple matches, take partial |
| {index: last - 3, count: 5, predicate: multipleItemPredicate, backwards: true }, // Multiple matches, skip/take partial |
| {index: 13, count: 10, predicate: multipleItemPredicate, backwards: true }, // Multiple matches, skip partial, take past end of collection |
| {index: 4, count: 13, predicate: allItemPredicate, backwards: true }, // All items match, skip/take partial |
| {index: last, count: 20, predicate: noItemPredicate, backwards: true }, // No matches |
| {index: 0, count: 0, predicate: allItemPredicate, backwards: true }, // Zero count |
| {index: -5, count: 1, predicate: allItemPredicate, backwards: true }, // Negative index |
| {index: last + 1, count: 1, predicate: allItemPredicate, backwards: true }, // Index past end of collection |
| |
| {index: "foo", count: 1, predicate: singleItemPredicate, exception: { message: "'index' must be a valid number.", index: NaN} }, |
| { index: 0, count: "foo", predicate: multipleItemPredicate, exception: { message: "'count' must be a valid number.", count: NaN} } |
| ]; |
| |
| var testDescription = function(test) { |
| return "filter [" + test.index + ", " + test.count + " " + (test.backwards ? "back" : "forward") + "] for predicate " + test.predicate; |
| }; |
| |
| var filterErrorCallback = function (err) { |
| if (err && err.message) { |
| djstest.fail(err.message); |
| } else { |
| djstest.fail("unexpected promise failure"); |
| } |
| doneAfterAllTests(); |
| }; |
| |
| var removeSafariExceptionProperties = function (err) { |
| /// <summary>Removes Safari-specific properties from an exception object</summary> |
| /// <params name="err" type="Exception">The exception object to operate on</param> |
| /// <returns type="Exception">The original exception object with the Safari-specific properties removed</returns> |
| var safariProperties = ["line", "expressionBeginOffset", "expressionEndOffset", "sourceId", "sourceURL"]; |
| |
| var result = {}; |
| $.each(err, function (property, value) { |
| if ($.inArray(property, safariProperties) === -1) { |
| result[property] = value; |
| } |
| }); |
| |
| return result; |
| }; |
| |
| ODataReadOracle.readJsonAcrossServerPages(foodsFeed, function (expectData) { |
| $.each(tests, function (_, test) { |
| createNewCache(options).then(function (cache) { |
| try { |
| var expectedResults = {}; |
| if (test.backwards) { |
| cache.filterBack(test.index, test.count, test.predicate).then(function (results) { |
| expectedResults = CacheOracle.getExpectedFilterResults(expectData, test.index, test.count, test.predicate, test.backwards); |
| djstest.assertAreEqualDeep(results, expectedResults, "results for " + testDescription(test)); |
| doneAfterAllTests(); |
| }, filterErrorCallback); |
| } else { |
| cache.filterForward(test.index, test.count, test.predicate).then(function (results) { |
| expectedResults = CacheOracle.getExpectedFilterResults(expectData, test.index, test.count, test.predicate, test.backwards); |
| djstest.assertAreEqualDeep(results, expectedResults, "results for " + testDescription(test)); |
| doneAfterAllTests(); |
| }, filterErrorCallback); |
| } |
| |
| if (test.exception) { |
| djstest.fail("expected exception for " + testDescription(test)); |
| doneAfterAllTests(); |
| } |
| } catch (err) { |
| if (test.exception) { |
| djstest.assertAreEqualDeep(removeSafariExceptionProperties(err), test.exception, "exception for " + testDescription(test)); |
| } else { |
| djstest.fail("unexpected exception for " + testDescription(test) + ": " + djstest.toString(err)); |
| } |
| doneAfterAllTests(); |
| } |
| }, thenFailTest); |
| }); |
| }); |
| }); |
| |
| djstest.addTest(function createDataCacheTest() { |
| var cache; |
| |
| // Verify the defaults. |
| cache = odatajs.createDataCache({ name: "name", source: "src" }); |
| |
| djstest.assertAreEqual(cache.onidle, undefined, "onidle is undefined"); |
| |
| // Verify specific values. |
| cache = odatajs.createDataCache({ name: "name", source: "src", cacheSize: 1, pageSize: 2, prefetchSize: 3, idle: 123 }); |
| |
| djstest.assertAreEqual(cache.onidle, 123, "onidle is as specified"); |
| |
| // Verify 0 pageSize |
| djstest.expectException(function () { |
| odatajs.createDataCache({ name: "name", source: "src", cacheSize: 1, pageSize: 0, prefetchSize: 3, idle: 123 }); |
| }, "zero pageSize"); |
| |
| // Verify negative pageSize |
| djstest.expectException(function () { |
| odatajs.createDataCache({ name: "name", source: "src", cacheSize: 1, pageSize: -2, prefetchSize: 3, idle: 123 }); |
| }, "negative pageSize"); |
| |
| // Verify NaN pageSize |
| djstest.expectException(function () { |
| cache = odatajs.createDataCache({ name: "name", source: "src", cacheSize: 1, pageSize: "2", prefetchSize: 3, idle: 123 }); |
| }, "NaN pageSize"); |
| |
| // Verify NaN cacheSize |
| djstest.expectException(function () { |
| cache = odatajs.createDataCache({ name: "name", source: "src", cacheSize: "1", pageSize: 2, prefetchSize: 3, idle: 123 }); |
| }, "NaN cacheSize"); |
| |
| // Verify NaN prefetchSize |
| djstest.expectException(function () { |
| cache = odatajs.createDataCache({ name: "name", source: "src", cacheSize: 1, pageSize: 2, prefetchSize: "3", idle: 123 }); |
| }, "NaN prefetchSize"); |
| |
| // Verify undefined name |
| djstest.expectException(function () { |
| odatajs.createDataCache({ source: "src", cacheSize: 1, pageSize: 1, prefetchSize: 3, idle: 123 }); |
| }, "undefined name"); |
| |
| // Verify null name |
| djstest.expectException(function () { |
| odatajs.createDataCache({ name: null, source: "src", cacheSize: 1, pageSize: 1, prefetchSize: 3, idle: 123 }); |
| }, "null name"); |
| |
| // Verify undefined source |
| djstest.expectException(function () { |
| odatajs.createDataCache({ name: "name", cacheSize: 1, pageSize: 1, prefetchSize: 3, idle: 123 }); |
| }, "undefined source"); |
| |
| djstest.done(); |
| }); |
| |
| djstest.addTest(function createDataCacheWithSourceTest() { |
| var cacheSource = { |
| count: function (success) { |
| djstest.pass("cacheSource.count was called"); |
| success(0); |
| }, |
| |
| read: function (index, count, success, error) { |
| djstest.assertAreEqual(index, 0, "index is the expected one"); |
| djstest.assertAreEqual(count, 10, "test is the expected one"); |
| djstest.assert(success, "success is defined"); |
| djstest.assert(error, "error is defined"); |
| djstest.pass("cacheSource.read was called"); |
| |
| setTimeout(function () { |
| success([]); |
| }, 0); |
| } |
| }; |
| |
| var cache = odatajs.createDataCache({ name: "name", source: cacheSource, mechanism: "memory", pageSize: 10 }); |
| cache.count().then(function () { |
| cache.readRange(0, 5).then(function () { |
| djstest.done(); |
| }, thenFailTest); |
| }, thenFailTest); |
| }); |
| |
| djstest.addTest(function cacheInitializationFailTest() { |
| // Tests various failure modes for cache initialization. |
| var failures = ["read-settings", "write-settings", "v2"]; |
| var failureIndex = 0; |
| |
| var originalStore = odatajs.createStore; |
| var restoreStore = function () { |
| odatajs.createStore = originalStore; |
| }; |
| |
| var storeError = { message: "cacheInitializationFailTest error" }; |
| odatajs.createStore = function (name, mechanism) { |
| return { |
| addOrUpdate: function (key, value, successCallback, errorCallback) { |
| if (failures[failureIndex] === "write-settings") { |
| window.setTimeout(function () { errorCallback(storeError); }, 2); |
| } else { |
| djstest.fail("Error unaccounted for in addOrUpdate for " + failures[failureIndex]); |
| window.setTimeout(function () { errorCallback(storeError); }, 2); |
| } |
| }, |
| read: function (key, successCallback, errorCallback) { |
| if (failures[failureIndex] === "read-settings") { |
| window.setTimeout(function () { errorCallback(storeError); }, 2); |
| } else if (failures[failureIndex] === "v2") { |
| window.setTimeout(function () { |
| successCallback("K", { version: "2.0" }); |
| }, 2); |
| } else if (failures[failureIndex] === "write-settings") { |
| window.setTimeout(function () { successCallback(null, null); }, 2); |
| } else { |
| djstest.fail("Error unaccounted for read in " + failures[failureIndex]); |
| window.setTimeout(function () { errorCallback(storeError); }, 2); |
| } |
| } |
| }; |
| }; |
| |
| var nextFailure = function () { |
| djstest.log("Failure mode: " + failures[failureIndex]); |
| var cache = odatajs.createDataCache({ name: "name", source: "foo", mechanism: "memory", pageSize: 10 }); |
| try { |
| // The first readRange should succeed, because the data cache isn't really initialized at this time. |
| cache.readRange(1, 2).then(djstest.failAndDoneCallback("No function should succeed"), function (err) { |
| djstest.expectException(function () { |
| cache.readRange(1, 2); |
| }, "readRange after store is invalidated"); |
| |
| djstest.expectException(function () { |
| cache.count(); |
| }, "count after store is invalidated"); |
| |
| djstest.expectException(function () { |
| cache.clear(); |
| }, "clear after store is invalidated"); |
| |
| djstest.expectException(function () { |
| cache.filterForward(1, 2); |
| }, "filterForward after store is invalidated"); |
| |
| djstest.expectException(function () { |
| cache.filterBack(1, 2); |
| }, "filterBack after store is invalidated"); |
| |
| djstest.expectException(function () { |
| cache.toObservable(); |
| }, "toObservable after store is invalidated"); |
| |
| failureIndex++; |
| if (failureIndex === failures.length) { |
| restoreStore(); |
| djstest.done(); |
| } else { |
| nextFailure(); |
| } |
| }); |
| } catch (outerError) { |
| djstest.fail("Unexpected failure for first .readRange: " + window.JSON.stringify(outerError)); |
| restoreStore(); |
| djstest.done(); |
| } |
| }; |
| |
| nextFailure(); |
| }); |
| |
| djstest.addTest(function createDataCacheWithSourceCallsErrorTest() { |
| var cacheSource = { |
| count: function () { |
| djstest.fail("cacheSource.count was called"); |
| }, |
| |
| read: function (index, count, success, error) { |
| setTimeout(function () { |
| error({ message: "source error" }); |
| }, 0); |
| } |
| }; |
| |
| var cache = odatajs.createDataCache({ name: "name", source: cacheSource, mechanism: "memory", pageSize: 10 }); |
| cache.readRange(0, 5).then(function () { |
| djstest.fail("unexpected call to then success"); |
| djstest.done(); |
| }, function (err) { |
| djstest.assertAreEqual(err.message, "source error", "got the expected error"); |
| djstest.done(); |
| }); |
| }); |
| |
| djstest.addTest(function toObservableMissingTest() { |
| createNewCache({ name: "cache", source: "http://temp.org" }).then(function (cache) { |
| var hiddenRx = window.Rx; |
| try { |
| window.Rx = null; |
| var error = null; |
| try { |
| cache.ToObservable(); |
| } catch (err) { |
| error = err; |
| } |
| |
| djstest.assert(error !== null, "error !== null"); |
| } finally { |
| window.Rx = hiddenRx; |
| } |
| |
| djstest.assert(error !== null, "error !== null"); |
| djstest.destroyCacheAndDone(cache); |
| }); |
| }); |
| |
| djstest.addTest(function toObservableSinglePageTest() { |
| createNewCache({ name: "cache", source: foodsFeed }).then(function (cache) { |
| var lastId = -1; |
| cache.ToObservable().Subscribe(function (item) { |
| djstest.assert(lastId < item.FoodID, "lastId < item.FoodID"); |
| lastId = item.FoodID; |
| }, thenFailTest, function () { |
| djstest.assert(lastId !== -1, "lastId !== -1"); |
| djstest.done(); |
| }); |
| }); |
| }); |
| |
| djstest.addTest(function toObservableCaseSinglePageTest() { |
| createNewCache({ name: "cache", source: foodsFeed }).then(function (cache) { |
| var lastId = -1; |
| cache.toObservable().Subscribe(function (item) { |
| djstest.assert(lastId < item.FoodID, "lastId < item.FoodID"); |
| lastId = item.FoodID; |
| }, thenFailTest, function () { |
| djstest.assert(lastId !== -1, "lastId !== -1"); |
| djstest.done(); |
| }); |
| }); |
| }); |
| |
| djstest.addTest(function toObservableMultiplePageTest() { |
| createNewCache({ name: "cache", source: foodsFeed, pageSize: 2 }).then(function (cache) { |
| var lastId = -1; |
| var callCount = 0; |
| cache.toObservable().Subscribe(function (item) { |
| djstest.assert(lastId < item.FoodID, "lastId < item.FoodID"); |
| lastId = item.FoodID; |
| callCount += 1; |
| }, thenFailTest, function () { |
| djstest.assert(lastId !== -1, "lastId !== -1"); |
| djstest.assert(callCount > 1, "callCount > 1"); |
| djstest.done(); |
| }); |
| }); |
| }); |
| |
| djstest.addTest(function toObservableEarlyDisposeTest() { |
| createNewCache({ name: "cache", source: foodsFeed, pageSize: 2 }).then(function (cache) { |
| var lastId = -1; |
| var callCount = 0; |
| var complete = false; |
| var observer = cache.toObservable().Subscribe(function (item) { |
| djstest.assert(complete === false, "complete === false"); |
| djstest.assert(lastId < item.FoodID, "lastId < item.FoodID"); |
| lastId = item.FoodID; |
| callCount += 1; |
| complete = true; |
| observer.Dispose(); |
| djstest.done(); |
| }, thenFailTest); |
| }); |
| }); |
| |
| djstest.addTest(function toObservableFailureTest() { |
| createNewCache({ name: "cache", source: foodsFeed, pageSize: 2 }).then(function (cache) { |
| var lastId = -1; |
| var complete = false; |
| window.MockHttpClient.clear().addResponse("*", { statusCode: 500, body: "server error" }); |
| window.MockHttpClient.async = true; |
| var savedClient = OData.defaultHttpClient; |
| OData.defaultHttpClient = window.MockHttpClient; |
| cache.toObservable().Subscribe(function (item) { |
| OData.defaultHttpClient = savedClient; |
| djstest.fail("Unexpected call to OnNext"); |
| }, function (err) { |
| djstest.assert(complete === false, "complete === false"); |
| djstest.assert(err, "err defined"); |
| OData.defaultHttpClient = savedClient; |
| complete = true; |
| djstest.done(); |
| }, function (complete) { |
| djstest.fail("Unexpected call to complete. Error handler should be called."); |
| OData.defaultHttpClient = savedClient; |
| complete = true; |
| djstest.done(); |
| }); |
| }); |
| }); |
| |
| // DATAJS INTERNAL START |
| |
| djstest.addTest(function createDeferredTest() { |
| // Verify basic use of deferred object. |
| var deferred = odatajs.createDeferred(); |
| deferred.then(function (val1, val2) { |
| djstest.assertAreEqual(val1, 1, "val1 is as specified"); |
| djstest.assertAreEqual(val2, 2, "val2 is as specified"); |
| djstest.done(); |
| }); |
| deferred.resolve(1, 2); |
| }); |
| |
| djstest.addTest(function deferredThenTest() { |
| // Verify then registration and chaining. |
| var deferred = odatajs.createDeferred(); |
| deferred.then(function (val1, val2) { |
| djstest.assertAreEqual(val1, 1, "val1 is as specified"); |
| djstest.assertAreEqual(val2, 2, "val2 is as specified"); |
| return "foo"; |
| }).then(function (foo) { |
| // See Compatibility Note B in DjsDeferred remarks. |
| djstest.assert(foo !== "foo", "argument for chained 'then' is *not* result of previous call"); |
| djstest.assert(foo === 1, "argument for chained 'then' is same as for previous call"); |
| |
| var other = odatajs.createDeferred(); |
| other.then(null, function (err, msg) { |
| djstest.assertAreEqual("error", err, "err is as specified"); |
| djstest.assertAreEqual("message", msg, "msg is as specified"); |
| |
| }).then(null, function (err, msg) { |
| djstest.log("chained errors are called"); |
| |
| djstest.assertAreEqual("error", err, "err is as specified"); |
| djstest.assertAreEqual("message", msg, "msg is as specified"); |
| |
| var multiple = odatajs.createDeferred(); |
| var count = 0; |
| |
| // See Compatibility Note A in DjsDeferred remarks. |
| multiple.then(function () { |
| djstest.assertAreEqual(count, 0, "first base registration fires as #0"); |
| count++; |
| }).then(function () { |
| djstest.assertAreEqual(count, 1, "first chained registration fires as #1"); |
| count++; |
| }); |
| |
| multiple.then(function () { |
| djstest.assertAreEqual(count, 2, "second base registration fires as #2"); |
| count++; |
| }).then(function () { |
| djstest.assertAreEqual(count, 3, "second chained registration fires as #3"); |
| djstest.done(); |
| }); |
| |
| multiple.resolve(); |
| }); |
| other.reject("error", "message"); |
| }); |
| |
| deferred.resolve(1, 2); |
| }); |
| |
| djstest.addTest(function deferredResolveTest() { |
| // Resolve with no arguments. |
| var deferred = odatajs.createDeferred(); |
| deferred.then(function (arg) { |
| djstest.assertAreEqual(arg, undefined, "resolve with no args shows up as undefined"); |
| |
| // Resolve with no callbacks. |
| var other = odatajs.createDeferred(); |
| other.resolve(); |
| djstest.done(); |
| }); |
| |
| deferred.resolve(); |
| }); |
| |
| djstest.addTest(function deferredRejectTest() { |
| // Resolve with no arguments. |
| var deferred = odatajs.createDeferred(); |
| deferred.then(null, function (arg) { |
| djstest.assertAreEqual(arg, undefined, "reject with no args shows up as undefined"); |
| |
| // Resolve with no callbacks. |
| var other = odatajs.createDeferred(); |
| other.reject(); |
| djstest.done(); |
| }); |
| |
| deferred.reject(); |
| }); |
| |
| djstest.addTest(function estimateSizeTest() { |
| var tests = [ |
| { i: null, e: 8 }, |
| { i: undefined, e: 8 }, |
| { i: 0, e: 8 }, |
| { i: "abc", e: 6 }, |
| { i: [1, 2, 3], e: 30 }, |
| { i: { a1: null, a2: undefined, a3: 5, a4: "ab", a5: { b1: 5, b2: 6} }, e: 72 }, |
| { i: {}, e: 0 } |
| ]; |
| |
| var i, len; |
| for (i = 0, len = tests.length; i < len; i++) { |
| var test = tests[i]; |
| djstest.assertAreEqual(odatajs.estimateSize(test.i), test.e); |
| } |
| djstest.done(); |
| }); |
| |
| djstest.addTest(function cacheOptionsTunnelTest() { |
| var mockClient = window.MockHttpClient; |
| var doneCalled = false; |
| |
| mockClient.clear().setAsync(true).addRequestVerifier("*", function (theRequest) { |
| if (!doneCalled) { |
| doneCalled = true; |
| djstest.assertAreEqual(theRequest.user, "the-user", "theRequest.user"); |
| djstest.assertAreEqual(theRequest.password, "the-password", "theRequest.password"); |
| djstest.assertAreEqual(theRequest.enableJsonpCallback, false, "theRequest.enableJsonpCallback"); |
| djstest.assertAreEqual(theRequest.callbackParameterName, "p", "callbackParameterName"); |
| djstest.done(); |
| } |
| }); |
| |
| var cache = odatajs.createDataCache({ |
| name: "cacheOptionsTunnel", |
| source: "http://foo-bar/", |
| user: "the-user", |
| password: "the-password", |
| enableJsonpCallback: false, |
| callbackParameterName: "p", |
| httpClient: mockClient |
| }); |
| |
| cache.readRange(0, 10).then(function (arr) { |
| djstest.fail("unexpected callback into readRange in test cacheOptionsTunnelTest"); |
| if (!doneCalled) { |
| doneCalled = true; |
| djstest.done(); |
| } |
| }); |
| }); |
| |
| djstest.addTest(function dataCacheHandlesFullStoreTest() { |
| |
| var TestStore = function (name) { |
| var that = new window.odatajs.MemoryStore(name); |
| that.addOrUpdate = function (key, value, success, error) { |
| if (key === "__settings") { |
| window.setTimeout(function () { |
| success(key, value); |
| }, 0); |
| } else { |
| window.setTimeout(function () { |
| error({ name: "QUOTA_EXCEEDED_ERR" }); |
| }, 0); |
| } |
| }; |
| return that; |
| }; |
| |
| TestStore.create = function (name) { |
| return new TestStore(name); |
| }; |
| |
| TestStore.isSupported = function () { |
| return true; |
| }; |
| |
| var cacheSource = { |
| identifier: "testSource", |
| count: function (success) { |
| djstest.fail("cacheSource.count was called"); |
| success(5); |
| }, |
| read: function (index, count, success, error) { |
| djstest.assertAreEqual(index, 0, "index is the expected one"); |
| djstest.assertAreEqual(count, 5, "test is the expected one"); |
| |
| setTimeout(function () { |
| success({ value: [1, 2, 3, 4, 5] }); |
| }, 0); |
| } |
| }; |
| |
| var originalCreateStore = window.odatajs.createStore; |
| |
| window.odatajs.createStore = function (name, mechanism) { |
| return TestStore(name); |
| }; |
| |
| try { |
| var cache = odatajs.createDataCache({ |
| name: "cache", |
| pageSize: 5, |
| prefetchSize: 0, |
| source: cacheSource, |
| mechanism: "teststore" |
| }); |
| } finally { |
| window.odatajs.createStore = originalCreateStore; |
| } |
| |
| cache.readRange(0, 5).then(function (data) { |
| djstest.assertAreEqual(data.value.length, 5, "5 items were read."); |
| cache.readRange(0, 5).then(function (data) { |
| djstest.assertAreEqual(data.value.length, 5, "5 items were read."); |
| djstest.done(); |
| }, thenFailTest); |
| }, thenFailTest); |
| }); |
| |
| // DATAJS INTERNAL END |
| })(this); |