From a35885bd2827b1461cebccf88e472244dd5dade6 Mon Sep 17 00:00:00 2001 From: guylabs Date: Fri, 28 Oct 2016 07:30:50 +0200 Subject: [PATCH] #23(feat): add fetchMultiple flag to resolve the same link multiple times --- README.md | 15 ++++++- dist/angular-spring-data-rest.js | 24 +++++----- dist/angular-spring-data-rest.min.js | 2 +- src/angular-spring-data-rest-provider.js | 24 +++++----- ...ar-spring-data-rest-provider.fetch.spec.js | 45 +++++++++++++++++++ test/angular-spring-data-rest.helper.spec.js | 37 +++++++++++++++ 6 files changed, 124 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index a6f511d..4662ef2 100644 --- a/README.md +++ b/README.md @@ -373,10 +373,21 @@ SpringDataRestAdapter.process(response, 'anotherLink', true).then(function(proce }); ``` -Now the response of the `anotherLink` will be processed the same way as the main response was processed. But *be aware* when setting the recursive flag to true, because when your reponses of the links contain the same link name again, then it will end up in a infinite loop. +Now the response of the `anotherLink` will be processed the same way as the main response was processed. But *be aware* when setting the recursive flag to true, because when your responses of the links contain the same link name again, then it will end up in a infinite loop. It will not fetch the `self` link as this would make no sense because the data is already in the response. The `self` key is also configurable. Read more [here](#configuration-of-the-springdatarestadapter). +As a last configuration option you are able to set the fourth parameter called `fetchMultiple` of the `process` method which is used to define if a link is fetched multiple times if the same link is contained in multiple links and you want to resolve each one of them. The drawback +is that it won't cache the responses in any way. So if you enable this then multiple network calls will be made. Here an example: + +```javascript +SpringDataRestAdapter.process(response, 'anotherLink', true, true).then(function(processedResponse) { + ... +}); +``` + +:warning: If you set the `fetchMultiple` to `true` you could end up in an infinite loop if you have circular dependencies in your `_links`. + #### Fetch multiple or all links If you want to fetch multiple links then you are able to add an array of strings with the given link names: @@ -552,7 +563,7 @@ myApp.config(function (SpringDataRestAdapterProvider) { SpringDataRestAdapterProvider.config({ fetchFunction: function (url, key, data, fetchLinkNames, recursive) { var $http = angular.injector(['ng']).get('$http'); - + $http.get('/rest/endpoint').then(function (responseData) { console.log(responseData); }) diff --git a/dist/angular-spring-data-rest.js b/dist/angular-spring-data-rest.js index e89db95..4134891 100644 --- a/dist/angular-spring-data-rest.js +++ b/dist/angular-spring-data-rest.js @@ -100,9 +100,11 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function () * @param {object} data the data object reference in which the response is stored * @param {[string]|string} fetchLinkNames the fetch link names to allow to process the fetched response * @param {boolean} recursive true if the fetched response should be processed recursively with the + * @param {boolean} fetchMultiple true if multiple same link names should be resolved. ATTENTION: this could lead to + * an infinite loop when you have circular dependencies between the links. * adapter, false otherwise */ - function fetchFunction(url, key, data, fetchLinkNames, recursive) { + function fetchFunction(url, key, data, fetchLinkNames, recursive, fetchMultiple) { if (config.fetchFunction == undefined) { var promisesArray = []; @@ -111,7 +113,7 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function () // wrap the response again with the adapter and return the promise if (recursive) { - return processData(responseData.data, fetchLinkNames, true).then(function (processedData) { + return processData(responseData.data, fetchLinkNames, true, fetchMultiple).then(function (processedData) { data[key] = processedData; }); } else { @@ -129,7 +131,7 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function () // wait for all promises to be resolved and return a new promise return $injector.get("$q").all(promisesArray); } else { - return config.fetchFunction(url, key, data, fetchLinkNames, recursive); + return config.fetchFunction(url, key, data, fetchLinkNames, recursive, fetchMultiple); } } @@ -142,9 +144,11 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function () * 'fetchAllLinkNamesKey' key from the config object to fetch all links except the 'self' key. * @param {boolean} recursive true if the automatically fetched response should be processed recursively with the * adapter, false otherwise + * @param {boolean} fetchMultiple true if multiple same link names should be resolved. ATTENTION: this could lead to + * an infinite loop when you have circular dependencies between the links. * @returns {object} the processed JSON data */ - var processData = function processDataFunction(promiseOrData, fetchLinkNames, recursive) { + var processData = function processDataFunction(promiseOrData, fetchLinkNames, recursive, fetchMultiple) { // convert the given promise or data to a $q promise return $injector.get("$q").when(promiseOrData).then(function (data) { @@ -274,12 +278,12 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function () // 2. the all link names key is given then fetch the link // 3. the given key is equal // 4. the given key is inside the array - if (linkMap[self].indexOf(linkName) < 0 && + if ((fetchMultiple || linkMap[self].indexOf(linkName) < 0) && (fetchLinkNames == config.fetchAllKey || (typeof fetchLinkNames === "string" && linkName == fetchLinkNames) || (fetchLinkNames instanceof Array && fetchLinkNames.indexOf(linkName) >= 0))) { promisesArray.push(fetchFunction(getProcessedUrl(data, linkName), linkName, - processedData, fetchLinkNames, recursive)); + processedData, fetchLinkNames, recursive, fetchMultiple)); linkMap[self].push(linkName); } } @@ -307,7 +311,7 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function () var processedDataArrayPromise; angular.forEach(value, function (arrayValue, arrayKey) { if (angular.isObject(arrayValue)) { - processedDataArrayPromise = processDataFunction({data: arrayValue}, fetchLinkNames, recursive).then(function (processedResponseData) { + processedDataArrayPromise = processDataFunction({data: arrayValue}, fetchLinkNames, recursive, fetchMultiple).then(function (processedResponseData) { processedDataArray[arrayKey] = processedResponseData; }); promisesArray.push(processedDataArrayPromise); @@ -324,7 +328,7 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function () } } else if (angular.isObject(value)) { // single objects are processed directly - promisesArray.push(processDataFunction({data: value}, fetchLinkNames, recursive).then(function (processedResponseData) { + promisesArray.push(processDataFunction({data: value}, fetchLinkNames, recursive, fetchMultiple).then(function (processedResponseData) { processedData[config.embeddedNewKey][key] = processedResponseData; })); } @@ -378,9 +382,9 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function () // empty the map and // return an object with the processData function return { - process: function(promiseOrData, fetchLinkNames, recursive) { + process: function (promiseOrData, fetchLinkNames, recursive, fetchMultiple) { linkMap = {}; - return processData(promiseOrData, fetchLinkNames, recursive); + return processData(promiseOrData, fetchLinkNames, recursive, fetchMultiple); } }; }] diff --git a/dist/angular-spring-data-rest.min.js b/dist/angular-spring-data-rest.min.js index 6b9e3db..22b4dd6 100644 --- a/dist/angular-spring-data-rest.min.js +++ b/dist/angular-spring-data-rest.min.js @@ -3,4 +3,4 @@ * Copyright 2016 Guy Brand (@guy_labs) * https://github.com/guylabs/angular-spring-data-rest */ -!function(){"use strict";function a(b){return angular.forEach(arguments,function(c){c!==b&&angular.forEach(c,function(c,d){b[d]&&b[d].constructor&&b[d].constructor===Object?a(b[d],c):b[d]=c})}),angular.copy(b)}function b(a,b,c,d){var e=a[b];if(e){var f={};if(f[c]={},d===!0)angular.forEach(Object.keys(e),function(a){f[c][a]=e[a]});else{var g=Object.keys(e)[0];f[c]=e[g]}a=angular.extend(a,f),delete a[b]}return a}function c(a,b){return b&&(a=e(a)),a}function d(a,b,c){if(void 0==a||!a)throw new Error("The provided resource name '"+b+"' has no valid URL in the '"+c+"' property.");return a}function e(a){return a.replace(/{.*}/g,"")}function f(a){var b={},c=/{\?(.*)}/g,d=c.exec(a)[1].split(",");return angular.forEach(d,function(a){b[a]=""}),b}angular.module("spring-data-rest",["ngResource"]),angular.module("spring-data-rest").provider("SpringDataRestAdapter",function(){var e={linksKey:"_links",linksHrefKey:"href",linksSelfLinkName:"self",embeddedKey:"_embedded",embeddedNewKey:"_embeddedItems",embeddedNamedResources:!1,resourcesKey:"_resources",resourcesFunction:void 0,fetchFunction:void 0,fetchAllKey:"_allLinks"};return{config:function(b){if("undefined"!=typeof b){if(!angular.isObject(b))throw new Error("The given configuration '"+b+"' is not an object.");if(void 0!=b.resourcesFunction&&"function"!=typeof b.resourcesFunction)throw new Error("The given resource function '"+b.resourcesFunction+"' is not of type function.");if(void 0!=b.fetchFunction&&"function"!=typeof b.fetchFunction)throw new Error("The given fetch function '"+b.fetchFunction+"' is not of type function.");e=a(e,b)}return e},$get:["$injector",function(a){function g(b,c,d,f){return void 0==e.resourcesFunction?a.get("$resource")(b,c,d,f):e.resourcesFunction(b,c,d,f)}function h(b,c,d,f,g){if(void 0==e.fetchFunction){var h=[];return h.push(a.get("$http").get(b).then(function(a){return g?j(a.data,f,!0).then(function(a){d[c]=a}):j(a.data).then(function(a){d[c]=a})},function(b){if(404!=b.status)return a.get("$q").reject(b)})),a.get("$q").all(h)}return e.fetchFunction(b,c,d,f,g)}var i={},j=function k(j,l,m){function n(a,b){var f=d(a[e.linksKey][b][e.linksHrefKey],b,e.linksHrefKey);return c(f,a[e.linksKey][b].templated)}function o(a){return"string"==typeof a&&a.indexOf("/")>0}function p(a){if(o(a)){var b=a.indexOf("/");return[a.substr(0,b),a.substr(b,a.length)]}}return a.get("$q").when(j).then(function(c){var d=function(a,b,d,h){var i=this[e.linksKey],j=b,k="";if(o(a)){var l=p(a);a=l[0],k=l[1]}if(angular.isObject(a)){if(!a.name)throw new Error("The provided resource object must contain a name property.");var m=a.parameters;return b&&angular.isObject(b)?j=m&&angular.isObject(m)?angular.extend(angular.copy(b),angular.copy(m)):angular.copy(b):m&&angular.isObject(m)&&(j=angular.copy(m)),angular.forEach(j,function(a,b){""===a&&delete j[b]}),g(n(c,a.name),j,d,h)}if(a in i)return g(n(c,a)+k,j,d,h);var q=[];return angular.forEach(i,function(a,b){if(a.templated){var c=f(a[e.linksHrefKey]);q.push({name:b,parameters:c})}else q.push({name:b})}),q};if(c&&c.data&&(c=c.data),!angular.isObject(c)||c instanceof Array)return a.get("$q").reject("Given data '"+c+"' is not of type object.");if(l&&!(l instanceof Array||"string"==typeof l))return a.get("$q").reject("Given fetch links '"+l+"' is not of type array or string.");var j=void 0,q=[];if(e.linksKey in c){var r={};if(r[e.resourcesKey]=d,j=angular.extend(angular.copy(c),r),void 0!=l){var s=c[e.linksKey][e.linksSelfLinkName][e.linksHrefKey];i[s]||(i[s]=[]),angular.forEach(c[e.linksKey],function(a,b){b!=e.linksSelfLinkName&&i[s].indexOf(b)<0&&(l==e.fetchAllKey||"string"==typeof l&&b==l||l instanceof Array&&l.indexOf(b)>=0)&&(q.push(h(n(c,b),b,j,l,m)),i[s].push(b))})}}return e.embeddedKey in c&&(j||(j=angular.copy(c)),j=b(j,e.embeddedKey,e.embeddedNewKey,e.embeddedNamedResources),angular.forEach(j[e.embeddedNewKey],function(a,b){if(a instanceof Array&&a.length>0){var c,d=[];angular.forEach(a,function(a,b){angular.isObject(a)?(c=k({data:a},l,m).then(function(a){d[b]=a}),q.push(c)):d[b]=a}),c&&c.then(function(){j[e.embeddedNewKey][b]=d})}else angular.isObject(a)&&q.push(k({data:a},l,m).then(function(a){j[e.embeddedNewKey][b]=a}))})),a.get("$q").all(q).then(function(){return j?j:c})})};return{process:function(a,b,c){return i={},j(a,b,c)}}}]}}),angular.module("spring-data-rest").provider("SpringDataRestInterceptor",["$httpProvider","SpringDataRestAdapterProvider",function(a){return{apply:function(){a.interceptors.push("SpringDataRestInterceptor")},$get:["SpringDataRestAdapter","$q",function(a,b){return{response:function(b){return!angular.isObject(b.data)||b.data instanceof Array?b:a.process(b.data).then(function(a){return b.data=a,b})}}}]}}])}(); \ No newline at end of file +!function(){"use strict";function a(b){return angular.forEach(arguments,function(c){c!==b&&angular.forEach(c,function(c,d){b[d]&&b[d].constructor&&b[d].constructor===Object?a(b[d],c):b[d]=c})}),angular.copy(b)}function b(a,b,c,d){var e=a[b];if(e){var f={};if(f[c]={},d===!0)angular.forEach(Object.keys(e),function(a){f[c][a]=e[a]});else{var g=Object.keys(e)[0];f[c]=e[g]}a=angular.extend(a,f),delete a[b]}return a}function c(a,b){return b&&(a=e(a)),a}function d(a,b,c){if(void 0==a||!a)throw new Error("The provided resource name '"+b+"' has no valid URL in the '"+c+"' property.");return a}function e(a){return a.replace(/{.*}/g,"")}function f(a){var b={},c=/{\?(.*)}/g,d=c.exec(a)[1].split(",");return angular.forEach(d,function(a){b[a]=""}),b}angular.module("spring-data-rest",["ngResource"]),angular.module("spring-data-rest").provider("SpringDataRestAdapter",function(){var e={linksKey:"_links",linksHrefKey:"href",linksSelfLinkName:"self",embeddedKey:"_embedded",embeddedNewKey:"_embeddedItems",embeddedNamedResources:!1,resourcesKey:"_resources",resourcesFunction:void 0,fetchFunction:void 0,fetchAllKey:"_allLinks"};return{config:function(b){if("undefined"!=typeof b){if(!angular.isObject(b))throw new Error("The given configuration '"+b+"' is not an object.");if(void 0!=b.resourcesFunction&&"function"!=typeof b.resourcesFunction)throw new Error("The given resource function '"+b.resourcesFunction+"' is not of type function.");if(void 0!=b.fetchFunction&&"function"!=typeof b.fetchFunction)throw new Error("The given fetch function '"+b.fetchFunction+"' is not of type function.");e=a(e,b)}return e},$get:["$injector",function(a){function g(b,c,d,f){return void 0==e.resourcesFunction?a.get("$resource")(b,c,d,f):e.resourcesFunction(b,c,d,f)}function h(b,c,d,f,g,h){if(void 0==e.fetchFunction){var i=[];return i.push(a.get("$http").get(b).then(function(a){return g?j(a.data,f,!0,h).then(function(a){d[c]=a}):j(a.data).then(function(a){d[c]=a})},function(b){if(404!=b.status)return a.get("$q").reject(b)})),a.get("$q").all(i)}return e.fetchFunction(b,c,d,f,g,h)}var i={},j=function j(k,l,m,n){function o(a,b){var f=d(a[e.linksKey][b][e.linksHrefKey],b,e.linksHrefKey);return c(f,a[e.linksKey][b].templated)}function p(a){return"string"==typeof a&&a.indexOf("/")>0}function q(a){if(p(a)){var b=a.indexOf("/");return[a.substr(0,b),a.substr(b,a.length)]}}return a.get("$q").when(k).then(function(c){var d=function(a,b,d,h){var i=this[e.linksKey],j=b,k="";if(p(a)){var l=q(a);a=l[0],k=l[1]}if(angular.isObject(a)){if(!a.name)throw new Error("The provided resource object must contain a name property.");var m=a.parameters;return b&&angular.isObject(b)?j=m&&angular.isObject(m)?angular.extend(angular.copy(b),angular.copy(m)):angular.copy(b):m&&angular.isObject(m)&&(j=angular.copy(m)),angular.forEach(j,function(a,b){""===a&&delete j[b]}),g(o(c,a.name),j,d,h)}if(a in i)return g(o(c,a)+k,j,d,h);var n=[];return angular.forEach(i,function(a,b){if(a.templated){var c=f(a[e.linksHrefKey]);n.push({name:b,parameters:c})}else n.push({name:b})}),n};if(c&&c.data&&(c=c.data),!angular.isObject(c)||c instanceof Array)return a.get("$q").reject("Given data '"+c+"' is not of type object.");if(l&&!(l instanceof Array||"string"==typeof l))return a.get("$q").reject("Given fetch links '"+l+"' is not of type array or string.");var k=void 0,r=[];if(e.linksKey in c){var s={};if(s[e.resourcesKey]=d,k=angular.extend(angular.copy(c),s),void 0!=l){var t=c[e.linksKey][e.linksSelfLinkName][e.linksHrefKey];i[t]||(i[t]=[]),angular.forEach(c[e.linksKey],function(a,b){b!=e.linksSelfLinkName&&(n||i[t].indexOf(b)<0)&&(l==e.fetchAllKey||"string"==typeof l&&b==l||l instanceof Array&&l.indexOf(b)>=0)&&(r.push(h(o(c,b),b,k,l,m,n)),i[t].push(b))})}}return e.embeddedKey in c&&(k||(k=angular.copy(c)),k=b(k,e.embeddedKey,e.embeddedNewKey,e.embeddedNamedResources),angular.forEach(k[e.embeddedNewKey],function(a,b){if(a instanceof Array&&a.length>0){var c,d=[];angular.forEach(a,function(a,b){angular.isObject(a)?(c=j({data:a},l,m,n).then(function(a){d[b]=a}),r.push(c)):d[b]=a}),c&&c.then(function(){k[e.embeddedNewKey][b]=d})}else angular.isObject(a)&&r.push(j({data:a},l,m,n).then(function(a){k[e.embeddedNewKey][b]=a}))})),a.get("$q").all(r).then(function(){return k?k:c})})};return{process:function(a,b,c,d){return i={},j(a,b,c,d)}}}]}}),angular.module("spring-data-rest").provider("SpringDataRestInterceptor",["$httpProvider","SpringDataRestAdapterProvider",function(a){return{apply:function(){a.interceptors.push("SpringDataRestInterceptor")},$get:["SpringDataRestAdapter","$q",function(a,b){return{response:function(b){return!angular.isObject(b.data)||b.data instanceof Array?b:a.process(b.data).then(function(a){return b.data=a,b})}}}]}}])}(); \ No newline at end of file diff --git a/src/angular-spring-data-rest-provider.js b/src/angular-spring-data-rest-provider.js index 589d70f..09a57bb 100644 --- a/src/angular-spring-data-rest-provider.js +++ b/src/angular-spring-data-rest-provider.js @@ -88,9 +88,11 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function () * @param {object} data the data object reference in which the response is stored * @param {[string]|string} fetchLinkNames the fetch link names to allow to process the fetched response * @param {boolean} recursive true if the fetched response should be processed recursively with the + * @param {boolean} fetchMultiple true if multiple same link names should be resolved. ATTENTION: this could lead to + * an infinite loop when you have circular dependencies between the links. * adapter, false otherwise */ - function fetchFunction(url, key, data, fetchLinkNames, recursive) { + function fetchFunction(url, key, data, fetchLinkNames, recursive, fetchMultiple) { if (config.fetchFunction == undefined) { var promisesArray = []; @@ -99,7 +101,7 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function () // wrap the response again with the adapter and return the promise if (recursive) { - return processData(responseData.data, fetchLinkNames, true).then(function (processedData) { + return processData(responseData.data, fetchLinkNames, true, fetchMultiple).then(function (processedData) { data[key] = processedData; }); } else { @@ -117,7 +119,7 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function () // wait for all promises to be resolved and return a new promise return $injector.get("$q").all(promisesArray); } else { - return config.fetchFunction(url, key, data, fetchLinkNames, recursive); + return config.fetchFunction(url, key, data, fetchLinkNames, recursive, fetchMultiple); } } @@ -130,9 +132,11 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function () * 'fetchAllLinkNamesKey' key from the config object to fetch all links except the 'self' key. * @param {boolean} recursive true if the automatically fetched response should be processed recursively with the * adapter, false otherwise + * @param {boolean} fetchMultiple true if multiple same link names should be resolved. ATTENTION: this could lead to + * an infinite loop when you have circular dependencies between the links. * @returns {object} the processed JSON data */ - var processData = function processDataFunction(promiseOrData, fetchLinkNames, recursive) { + var processData = function processDataFunction(promiseOrData, fetchLinkNames, recursive, fetchMultiple) { // convert the given promise or data to a $q promise return $injector.get("$q").when(promiseOrData).then(function (data) { @@ -262,12 +266,12 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function () // 2. the all link names key is given then fetch the link // 3. the given key is equal // 4. the given key is inside the array - if (linkMap[self].indexOf(linkName) < 0 && + if ((fetchMultiple || linkMap[self].indexOf(linkName) < 0) && (fetchLinkNames == config.fetchAllKey || (typeof fetchLinkNames === "string" && linkName == fetchLinkNames) || (fetchLinkNames instanceof Array && fetchLinkNames.indexOf(linkName) >= 0))) { promisesArray.push(fetchFunction(getProcessedUrl(data, linkName), linkName, - processedData, fetchLinkNames, recursive)); + processedData, fetchLinkNames, recursive, fetchMultiple)); linkMap[self].push(linkName); } } @@ -295,7 +299,7 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function () var processedDataArrayPromise; angular.forEach(value, function (arrayValue, arrayKey) { if (angular.isObject(arrayValue)) { - processedDataArrayPromise = processDataFunction({data: arrayValue}, fetchLinkNames, recursive).then(function (processedResponseData) { + processedDataArrayPromise = processDataFunction({data: arrayValue}, fetchLinkNames, recursive, fetchMultiple).then(function (processedResponseData) { processedDataArray[arrayKey] = processedResponseData; }); promisesArray.push(processedDataArrayPromise); @@ -312,7 +316,7 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function () } } else if (angular.isObject(value)) { // single objects are processed directly - promisesArray.push(processDataFunction({data: value}, fetchLinkNames, recursive).then(function (processedResponseData) { + promisesArray.push(processDataFunction({data: value}, fetchLinkNames, recursive, fetchMultiple).then(function (processedResponseData) { processedData[config.embeddedNewKey][key] = processedResponseData; })); } @@ -366,9 +370,9 @@ angular.module("spring-data-rest").provider("SpringDataRestAdapter", function () // empty the map and // return an object with the processData function return { - process: function(promiseOrData, fetchLinkNames, recursive) { + process: function (promiseOrData, fetchLinkNames, recursive, fetchMultiple) { linkMap = {}; - return processData(promiseOrData, fetchLinkNames, recursive); + return processData(promiseOrData, fetchLinkNames, recursive, fetchMultiple); } }; }] diff --git a/test/angular-spring-data-rest-provider.fetch.spec.js b/test/angular-spring-data-rest-provider.fetch.spec.js index 9cbbcd9..eaad28a 100644 --- a/test/angular-spring-data-rest-provider.fetch.spec.js +++ b/test/angular-spring-data-rest-provider.fetch.spec.js @@ -123,6 +123,51 @@ describe("the fetch function", function () { this.httpBackend.verifyNoOutstandingExpectation(); }); + it("must call all links with the same name of the given fetch link names array", function () { + + var embeddedNewKey = this.config.embeddedNewKey; + var fetchLinkNames = ['testCategory']; + + // the correct link href url + var testTestCategoryHref = 'http://localhost:8080/categories/b5ba38d5-98d3-4579-8709-a28549406697/testCategory'; + var testTestCategory2Href = 'http://localhost:8080/categories/b5ba38d5-98d3-4579-8709-a28549406697/testCategory2'; + + // check if the underlying fetch function is called with the correct href + var testTestCategoryExpectedResult = { + "version": 0, + "creationDate": 1406219870650, + "modificationDate": 1406219870650, + "name": "Test category 1", + "_links": { + "self": { + "href": "http://localhost:8080/categories/f974f5ef-a951-43b4-9027-4d2163216e54" + }, + "testCategory": { + "href": "http://localhost:8080/categories/b5ba38d5-98d3-4579-8709-a28549406697/testCategory2" + } + } + }; + var testTestCategory2ExpectedResult = {testCategory: 'test'}; + + this.httpBackend.whenGET(testTestCategoryHref). + respond(200, testTestCategoryExpectedResult); + this.httpBackend.expectGET(testTestCategoryHref); + + this.httpBackend.whenGET(testTestCategory2Href). + respond(200, testTestCategory2ExpectedResult); + this.httpBackend.expectGET(testTestCategory2Href); + + SpringDataRestAdapter.process(mockDataWithMultipleEmbeddedItemsAndSameLinks(), fetchLinkNames, true, true).then(function (processedData) { + // expect the fetched objects + expect(processedData[embeddedNewKey][0][fetchLinkNames[0]].name).toEqual("Test category 1"); + expect(processedData[embeddedNewKey][0][fetchLinkNames[0]][fetchLinkNames[0]].testCategory).toEqual("test"); + }); + + this.httpBackend.flush(); + this.httpBackend.verifyNoOutstandingRequest(); + this.httpBackend.verifyNoOutstandingExpectation(); + }); + it("must process the fetched response recursively if the flag is set", function () { var embeddedNewKey = this.config.embeddedNewKey; diff --git a/test/angular-spring-data-rest.helper.spec.js b/test/angular-spring-data-rest.helper.spec.js index ab5ff8c..831c33c 100644 --- a/test/angular-spring-data-rest.helper.spec.js +++ b/test/angular-spring-data-rest.helper.spec.js @@ -392,3 +392,40 @@ var mockDataAccident = function() { } }); }; + +var mockDataWithMultipleEmbeddedItemsAndSameLinks = function () { + return angular.copy( + { + "_links": { + "self": { + "href": "http://localhost:8080/categories{?page,size,sort}", + "templated": true + } + }, + "_embedded": { + "categories": [ + { + "version": 0, + "creationDate": 1406219870650, + "modificationDate": 1406219870650, + "name": "Test category 1", + "_links": { + "self": { + "href": "http://localhost:8080/categories/f974f5ef-a951-43b4-9027-4d2163216e54" + }, + "testCategory": { + "href": "http://localhost:8080/categories/b5ba38d5-98d3-4579-8709-a28549406697/testCategory" + } + } + } + ] + }, + "page": { + "size": 20, + "totalElements": 2, + "totalPages": 1, + "number": 0 + } + } + ); +}; \ No newline at end of file