diff --git a/README.md b/README.md index 9fd46c61..aea15823 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,23 @@ -#Restangular +# Restangular [![Build Status](https://travis-ci.org/mgonto/restangular.svg?branch=master)](https://travis-ci.org/mgonto/restangular) [![Coverage Status](https://coveralls.io/repos/github/mgonto/restangular/badge.svg?branch=master)](https://coveralls.io/github/mgonto/restangular?branch=master) [![David](https://img.shields.io/david/dev/mgonto/restangular.svg)](https://david-dm.org/mgonto/restangular/?type=dev) +[![Known Vulnerabilities](https://snyk.io/test/github/mgonto/restangular/badge.svg)](https://snyk.io/test/github/mgonto/restangular) [![PayPayl donate button](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=martin%40gon%2eto&lc=US&item_name=Martin%20Gontovnikas¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted "Donate once-off to this project using Paypal") [![Donate on Gittip](http://img.shields.io/gittip/mgonto.svg)](https://www.gittip.com/mgonto/) -Restangular is an AngularJS service that simplifies common GET, POST, DELETE, and UPDATE requests with a minimum of client code. -It's a perfect fit for any WebApp that consumes data from a RESTful API. +Restangular is an AngularJS service that simplifies common GET, POST, DELETE, and UPDATE requests with a minimum of client code. It's a perfect fit for any WebApp that consumes data from a RESTful API. -Try the [live demo on plunkr](http://plnkr.co/edit/8qrGeE?p=preview). It uses the same example as the official [Angular Javascript Project](http://angularjs.org/#wire-up-a-backend), but with Restangular! +**Note This version of Restangular [only supports Angular 1](#supported-angular-versions). For an Angular 2+ version of Restangular, check out [ngx-restangular](https://github.com/2muchcoffeecom/ngx-restangular).** It's a separate project with different maintainers, so issues regarding ngx-restangular should be reported [over there](https://github.com/2muchcoffeecom/ngx-restangular/issues) :wink: -Watch [a video introduction of a talk I gave at Devoxx France](http://www.parleys.com/play/535a189ee4b0c5ba17d43455/chapter1/about) about Restangular. +Learn Restangular! Try the [live demo on plunkr](http://plnkr.co/edit/8qrGeE?p=preview). It uses the same example as the official [Angular Javascript Project](http://angularjs.org/#wire-up-a-backend), but with Restangular! Or watch [a video introduction of a talk I gave at Devoxx France](http://www.parleys.com/play/535a189ee4b0c5ba17d43455/chapter1/about) about Restangular. -#Table of contents + +# Table of contents - [Restangular](#restangular) - [Differences with $resource](#differences-with-resource) @@ -137,7 +138,7 @@ $scope.user.one('messages', 123).one('from', 123).getList('unread'); **[Back to top](#table-of-contents)** -#How do I add this to my project? +## How do I add this to my project? You can download this by: @@ -154,7 +155,7 @@ You can download this by: **[Back to top](#table-of-contents)** -#Dependencies +## Dependencies Restangular depends on Angular and Lodash (or Underscore). @@ -172,7 +173,7 @@ Each time, there're more Production WebApps using `Restangular`. If your webapp **[Back to top](#table-of-contents)** -#Starter Guide +# Starter Guide ## Quick Configuration (For Lazy Readers) This is all you need to start using all the basic Restangular features. diff --git a/test/restangularSpec.js b/test/restangularSpec.js index 13452748..fd989819 100644 --- a/test/restangularSpec.js +++ b/test/restangularSpec.js @@ -1075,23 +1075,87 @@ describe('Restangular', function () { }); describe('getRestangularUrl', function () { + it('should get the URL for the current object', function () { + var element = Restangular.one('accounts', 123); + expect(element.getRestangularUrl()).toEqual('/accounts/123'); + }); + it('should not include query parameters', function () { + var responseHandler = jasmine.createSpy(), element; + $httpBackend.expectGET('/accounts/123?query=params').respond({id: 123, name: 'account123'}); + Restangular.one('accounts', 123).get({query: 'params'}).then(responseHandler); + $httpBackend.flush(); + + element = responseHandler.calls.argsFor(0)[0]; + expect(element.getRestangularUrl()).toEqual('/accounts/123'); + }); + it('should be the same for the built resource as for the fetched resource', function () { + var responseHandler = jasmine.createSpy(), element, resource; + $httpBackend.expectGET('/accounts/123').respond({id: 123, name: 'Account 123'}); + resource = Restangular.one('accounts', 123); + resource.get().then(responseHandler); + $httpBackend.flush(); + + element = responseHandler.calls.argsFor(0)[0]; + expect(resource.getRestangularUrl()).toEqual('/accounts/123'); + expect(element.getRestangularUrl()).toEqual('/accounts/123'); + }); + it('should use the id from the response, not the request', function () { + var responseHandler = jasmine.createSpy(), element, resource; + $httpBackend.expectGET('/accounts/123').respond({id: 444, name: 'Account 444'}); + resource = Restangular.one('accounts', 123); + resource.get().then(responseHandler); + $httpBackend.flush(); + + element = responseHandler.calls.argsFor(0)[0]; + expect(resource.getRestangularUrl()).toEqual('/accounts/123'); + expect(element.getRestangularUrl()).toEqual('/accounts/444'); + }); + it('should have an empty id in the URL if the response id is empty', function () { + // https://github.com/mgonto/restangular/issues/1421 + var responseHandler = jasmine.createSpy(), element, resource; + $httpBackend.expectGET('/accounts/123').respond({name: 'Account 444'}); + resource = Restangular.one('accounts', 123); + resource.get().then(responseHandler); + $httpBackend.flush(); + + element = responseHandler.calls.argsFor(0)[0]; + expect(resource.getRestangularUrl()).toEqual('/accounts/123'); + expect(element.getRestangularUrl()).toEqual('/accounts'); + }); + it('should return the generated URL for PUTed elements', function () { + var responseHandler = jasmine.createSpy(), element; + $httpBackend.expectPUT('/accounts/123').respond({id: 123, name: 'Account 123'}); + Restangular.one('accounts', 123).put().then(responseHandler); + $httpBackend.flush(); + + element = responseHandler.calls.argsFor(0)[0]; + expect(element.getRestangularUrl()).toEqual('/accounts/123'); + }); + it('should return the generated URL for POSTed elements', function () { + var responseHandler = jasmine.createSpy(), element; + $httpBackend.expectPOST('/accounts').respond({id: 123, name: 'Account 123'}); + Restangular.restangularizeElement(null, {name: 'Account 123'}, 'accounts', false, false).save().then(responseHandler); + $httpBackend.flush(); + + element = responseHandler.calls.argsFor(0)[0]; + expect(element.getRestangularUrl()).toEqual('/accounts/123'); + }); it('should return the generated URL when you chain Restangular methods together', function () { var restangularSpaces = Restangular.one('accounts', 123).one('buildings', 456).all('spaces'); expect(restangularSpaces.getRestangularUrl()).toEqual('/accounts/123/buildings/456/spaces'); }); - }); - describe('getRestangularUrl with useCannonicalId set to true', function () { - it('should return the generated URL when you chain Restangular methods together', function () { - var R = Restangular.withConfig(function (config) { - config.setUseCannonicalId(true); + describe('with useCannonicalId set to true', function () { + it('should return the generated URL when you chain Restangular methods together', function () { + var R = Restangular.withConfig(function (config) { + config.setUseCannonicalId(true); + }); + var restangularSpaces = R.one('accounts', 123).one('buildings', 456).all('spaces'); + expect(restangularSpaces.getRestangularUrl()).toEqual('/accounts/123/buildings/456/spaces'); }); - var restangularSpaces = R.one('accounts', 123).one('buildings', 456).all('spaces'); - expect(restangularSpaces.getRestangularUrl()).toEqual('/accounts/123/buildings/456/spaces'); }); }); - describe('addElementTransformer', function () { it('should allow for a custom method to be placed at the collection level', function () { var accountsPromise; @@ -1501,30 +1565,152 @@ describe('Restangular', function () { }); describe('setPlainByDefault', function () { + var plainByDefaultRestangular; - it('should not add restangularized methods to response object', function () { - var newRes = Restangular.withConfig(function (RestangularConfigurer) { + beforeEach(function () { + plainByDefaultRestangular = Restangular.withConfig(function (RestangularConfigurer) { RestangularConfigurer.setPlainByDefault(true); }); + }); - expect(newRes.configuration.plainByDefault).toEqual(true); + it('should set the property on the configuration', function () { + expect(plainByDefaultRestangular.configuration.plainByDefault).toEqual(true); + }); - newRes.one('accounts', 0).get().then(function (account) { + it('should not add restangularized methods to response object', function () { + plainByDefaultRestangular.one('accounts', 0).get().then(function (account) { expect(account).toEqual(testData.accountsModel[0]); }); - $httpBackend.flush(); }); it('shoud not add restangularized methods to response collection', function () { - var newRes = Restangular.withConfig(function (RestangularConfigurer) { - RestangularConfigurer.setPlainByDefault(true); + plainByDefaultRestangular.all('accounts').getList().then(function (accounts) { + expect(accounts).toEqual(testData.accountsModel); }); + $httpBackend.flush(); + }); - newRes.all('accounts').getList().then(function (accounts) { - expect(accounts).toEqual(testData.accountsModel); + describe('with ETag', function () { + beforeEach(function () { + $httpBackend.whenGET('/accounts').respond( + testData.accountsModel, + {'ETag': 'c11ea3f8-3bfd-4be8-a6a6-501dd831b8a4'} + ); + $httpBackend.whenGET('/accounts/1').respond( + testData.accountsModel[1], + {'ETag': 'bf79b780-f132-4f44-a9eb-7e6eb4f902b2'} + ); + }); + + it('should not add restangularized ETag to response object', function () { + plainByDefaultRestangular.one('accounts', 0).get().then(function (account) { + expect(account).toEqual(testData.accountsModel[0]); + }); + $httpBackend.flush(); }); + + it('shoud not add restangularized ETag to response collection', function () { + plainByDefaultRestangular.all('accounts').getList().then(function (accounts) { + expect(accounts).toEqual(testData.accountsModel); + }); + $httpBackend.flush(); + }); + }); + }); + + describe('ETags', function () { + beforeEach(function () { + $httpBackend.whenGET('/etagAccounts').respond( + testData.accountsModel, + {'ETag': 'c11ea3f8-3bfd-4be8-a6a6-501dd831b8a4'} + ); + $httpBackend.whenGET('/etagAccounts/1').respond( + testData.accountsModel[1], + {'ETag': 'bf79b780-f132-4f44-a9eb-7e6eb4f902b2'} + ); + }); + + it('should include the ETag in the restangularized element', function () { + Restangular.one('etagAccounts', 1).get().then(function (account) { + expect(account.restangularEtag).toEqual('bf79b780-f132-4f44-a9eb-7e6eb4f902b2'); + }); + $httpBackend.flush(); + }); + it('should include the ETag in the restangularized collection', function () { + Restangular.all('etagAccounts').getList().then(function (accounts) { + expect(accounts.restangularEtag).toEqual('c11ea3f8-3bfd-4be8-a6a6-501dd831b8a4'); + }); + $httpBackend.flush(); + }); + it('should add the If-Match header on PUT requests', function () { + var responseHandler = jasmine.createSpy(); + Restangular.one('etagAccounts', 1).get().then(responseHandler); + $httpBackend.flush(); + + var account = responseHandler.calls.argsFor(0)[0]; + $httpBackend.expect( + 'PUT', + '/etagAccounts/1', + testData.accountsModel[1], + function (headers) { + return headers['If-Match'] === 'bf79b780-f132-4f44-a9eb-7e6eb4f902b2'; + } + ).respond(200); + account.save(); + $httpBackend.flush(); + }); + it('should add the If-Match header on DELETE requests', function () { + var responseHandler = jasmine.createSpy(); + Restangular.one('etagAccounts', 1).get().then(responseHandler); + $httpBackend.flush(); + + var account = responseHandler.calls.argsFor(0)[0]; + $httpBackend.expect( + 'DELETE', + '/etagAccounts/1', + testData.accountsModel[1], + function (headers) { + return headers['If-Match'] === 'bf79b780-f132-4f44-a9eb-7e6eb4f902b2'; + } + ).respond(200); + account.remove(); $httpBackend.flush(); }); + it('should add the If-None-Match header on GET requests for elements', function () { + var responseHandler = jasmine.createSpy(); + Restangular.one('etagAccounts', 1).get().then(responseHandler); + $httpBackend.flush(); + + var account = responseHandler.calls.argsFor(0)[0]; + $httpBackend.expect( + 'GET', + '/etagAccounts/1', + undefined, + function (headers) { + return headers['If-None-Match'] === 'bf79b780-f132-4f44-a9eb-7e6eb4f902b2'; + } + ).respond(200); + account.get(); + $httpBackend.flush(); + }); + it('should add the If-None-Match header on GET requests for collections', function () { + var responseHandler = jasmine.createSpy(); + Restangular.all('etagAccounts').getList().then(responseHandler); + $httpBackend.flush(); + + var accounts = responseHandler.calls.argsFor(0)[0]; + $httpBackend.expect( + 'GET', + '/etagAccounts', + undefined, + function (headers) { + return headers['If-None-Match'] === 'c11ea3f8-3bfd-4be8-a6a6-501dd831b8a4'; + } + ).respond(200); + accounts.getList(); + $httpBackend.flush(); + }); + }); });