describe('railsResourceFactory', function () { 'use strict'; beforeEach(function() { module('rails'); angular.module('rails').factory('railsTestInterceptor', function () { return function (promise) { return promise.then(function (response) { response.data.interceptorAdded = 'x'; return response; }); } }); }); describe('singular', function() { var $httpBackend, $rootScope, factory, Test, testInterceptor, config = { url: '/test', name: 'test' }; beforeEach(inject(function (_$httpBackend_, _$rootScope_, railsResourceFactory, railsTestInterceptor) { $httpBackend = _$httpBackend_; $rootScope = _$rootScope_; factory = railsResourceFactory; Test = railsResourceFactory(config); testInterceptor = railsTestInterceptor; })); afterEach(function() { $httpBackend.verifyNoOutstandingExpectation(); $httpBackend.verifyNoOutstandingRequest(); }); it('query should return resource object when response is single object', function () { var promise, result; $httpBackend.expectGET('/test').respond(200, {test: {abc: 'xyz'}}); expect(promise = Test.query()).toBeDefined(); promise.then(function (response) { result = response; }); $httpBackend.flush(); expect(result).toBeInstanceOf(Test); expect(result).toEqualData({abc: 'xyz'}); }); it('query should return no data on 204', function () { var promise, result; $httpBackend.expectGET('/test').respond(204); expect(promise = Test.query()).toBeDefined(); promise.then(function (response) { result = response; }); $httpBackend.flush(); expect(result).toBeUndefined(); }); it('query should add parameter abc=1', function () { var promise; $httpBackend.expectGET('/test?abc=1').respond(200, {test: {abc: 'xyz'}}); expect(promise = Test.query({abc: '1'})).toBeDefined(); $httpBackend.flush(); }); it('query should add parameters abc=1 & xyz=2', function () { var promise; $httpBackend.expectGET('/test?abc=1&xyz=2').respond(200, {test: {abc: 'xyz'}}); expect(promise = Test.query({abc: '1', xyz: 2})).toBeDefined(); $httpBackend.flush(); }); it('query should underscore parameters abc_xyz=1 & test=2', function () { var promise; $httpBackend.expectGET('/test?abc_xyz=1&test=2').respond(200, {test: {abc: 'xyz'}}); expect(promise = Test.query({abcXyz: '1', test: 2})).toBeDefined(); $httpBackend.flush(); }); it('query should not underscore parameters abcXyz=1 & test=2', function () { var promise; $httpBackend.expectGET('/test?abcXyz=1&test=2').respond(200, {test: {abc: 'xyz'}}); Test = factory(angular.extend({underscoreParams: false}, config)); expect(promise = Test.query({abcXyz: '1', test: 2})).toBeDefined(); $httpBackend.flush(); }); it('Parameter underscoring should not modify the defaultParams.', function () { var promise; $httpBackend.expectGET('/test?abc_xyz=1').respond(200, {test: {abc: 'xyz'}}); Test = factory(angular.extend({defaultParams: {abcXyz: 1}}, config)); expect(promise = Test.query()).toBeDefined(); $httpBackend.flush(); expect(Test.config.defaultParams.abcXyz).toBeDefined(); expect(Test.config.defaultParams.abc_xyz).not.toBeDefined(); }); it('query with default params should add parameter abc=1', function () { var promise, resource, defaultParamsConfig = {}; $httpBackend.expectGET('/test?abc=1').respond(200, {test: {abc: 'xyz'}}); angular.copy(config, defaultParamsConfig); defaultParamsConfig.defaultParams = {abc: '1'}; resource = factory(defaultParamsConfig); expect(promise = resource.query()).toBeDefined(); $httpBackend.flush(); }); it('query with default params and additional parameters should not modify default params', function () { var promise, resource, defaultParamsConfig = {}; $httpBackend.expectGET('/test?abc=1&xyz=2').respond(200, {test: {abc: 'xyz'}}); angular.copy(config, defaultParamsConfig); defaultParamsConfig.defaultParams = {abc: '1'}; resource = factory(defaultParamsConfig); expect(promise = resource.query({xyz: '2'})).toBeDefined(); $httpBackend.flush(); expect(resource.config.defaultParams).toEqualData({abc: '1'}); }); it('get should return resource object when response is 200', function () { var promise, result; $httpBackend.expectGET('/test/123').respond(200, {test: {id: 123, abc: 'xyz'}}); expect(promise = Test.get(123)).toBeDefined(); promise.then(function (response) { result = response; }); $httpBackend.flush(); expect(result).toBeInstanceOf(Test); expect(result).toEqualData({id: 123, abc: 'xyz'}); }); it('get should work with id as string as well', function () { var promise, result; $httpBackend.expectGET('/test/123').respond(200, {test: {id: 123, abc: 'xyz'}}); expect(promise = Test.get('123')).toBeDefined(); promise.then(function (response) { result = response; }); $httpBackend.flush(); expect(result).toBeInstanceOf(Test); expect(result).toEqualData({id: 123, abc: 'xyz'}); }); it('get should call failure callback when 404', function () { var promise, success = false, failure = false; $httpBackend.expectGET('/test/123').respond(404); expect(promise = Test.get(123)).toBeDefined(); promise.then(function () { success = true; }, function () { failure = true; }); $httpBackend.flush(); expect(success).toBe(false); expect(failure).toBe(true); }); it('get with default params should add parameter abc=1', function () { var promise, resource, defaultParamsConfig = {}; $httpBackend.expectGET('/test/123?abc=1').respond(200, {test: {abc: 'xyz'}}); angular.copy(config, defaultParamsConfig); defaultParamsConfig.defaultParams = {abc: '1'}; resource = factory(defaultParamsConfig); expect(promise = resource.get(123)).toBeDefined(); $httpBackend.flush(); }); it('should be able to create new instance and save it', function () { var data = new Test({abcDef: 'xyz'}); $httpBackend.expectPOST('/test', {test: {abc_def: 'xyz'}}).respond(200, {test: {id: 123, abc_def: 'xyz'}}); data.create(); $httpBackend.flush(); expect(data).toEqualData({id: 123, abcDef: 'xyz'}); }); it("should return a promise when calling save", function () { var promise, data; data = new Test({abc_def: 'xyz'}); $httpBackend.expectPOST('/test', {test: {abc_def: 'xyz'}}).respond(200, {test: {id: 123, abc_def: 'xyz'}}); expect(promise = data.save()).toBeDefined(); $httpBackend.flush() }); it('should be able to create new instance and save it using save', function () { var data = new Test({abcDef: 'xyz'}); $httpBackend.expectPOST('/test', {test: {abc_def: 'xyz'}}).respond(200, {test: {id: 123, abc_def: 'xyz'}}); data.save(); $httpBackend.flush(); expect(data).toEqualData({id: 123, abcDef: 'xyz'}); }); it('should be able to create new instance and update it', function () { var data = new Test({abcDef: 'xyz'}); $httpBackend.expectPOST('/test', {test: {abc_def: 'xyz'}}).respond(200, {test: {id: 123, abc_def: 'xyz'}}); data.create(); $httpBackend.flush(1); expect(data).toEqualData({id: 123, abcDef: 'xyz'}); $httpBackend.expectPUT('/test/123', {test: {abc_def: 'xyz', id: 123, xyz: 'abc'}}).respond(200, {test: {id: 123, abc_def: 'xyz', xyz: 'abc', extra: 'test'}}); data.xyz = 'abc'; data.update(); $httpBackend.flush(); expect(data).toEqualData({id: 123, abcDef: 'xyz', xyz: 'abc', extra: 'test'}); }); it('should be able to create new instance and update it using save', function () { var data = new Test({abcDef: 'xyz'}); $httpBackend.expectPOST('/test', {test: {abc_def: 'xyz'}}).respond(200, {test: {id: 123, abc_def: 'xyz'}}); data.save(); $httpBackend.flush(1); expect(data).toEqualData({id: 123, abcDef: 'xyz'}); $httpBackend.expectPUT('/test/123', {test: {abc_def: 'xyz', id: 123, xyz: 'abc'}}).respond(200, {test: {id: 123, abc_def: 'xyz', xyz: 'abc', extra: 'test'}}); data.xyz = 'abc'; data.save(); $httpBackend.flush(); expect(data).toEqualData({id: 123, abcDef: 'xyz', xyz: 'abc', extra: 'test'}); }); it('should be able to create new instance and update it using PATCH', function () { var promise, Resource, data, defaultParamsConfig = {}; angular.copy(config, defaultParamsConfig); defaultParamsConfig.updateMethod = 'patch'; Resource = factory(defaultParamsConfig); data = new Resource({abcDef: 'xyz'}); $httpBackend.expectPOST('/test', {test: {abc_def: 'xyz'}}).respond(200, {test: {id: 123, abc_def: 'xyz'}}); data.create(); $httpBackend.flush(1); expect(data).toEqualData({id: 123, abcDef: 'xyz'}); $httpBackend.expectPATCH('/test/123', {test: {abc_def: 'xyz', id: 123, xyz: 'abc'}}).respond(200, {test: {id: 123, abc_def: 'xyz', xyz: 'abc', extra: 'test'}}); data.xyz = 'abc'; data.update(); $httpBackend.flush(); expect(data).toEqualData({id: 123, abcDef: 'xyz', xyz: 'abc', extra: 'test'}); }); it('should be able to create new instance and update it using save using PATCH', function () { var promise, Resource, data, defaultParamsConfig = {}; angular.copy(config, defaultParamsConfig); defaultParamsConfig.updateMethod = 'patch'; Resource = factory(defaultParamsConfig); data = new Resource({abcDef: 'xyz'}); $httpBackend.expectPOST('/test', {test: {abc_def: 'xyz'}}).respond(200, {test: {id: 123, abc_def: 'xyz'}}); data.save(); $httpBackend.flush(1); expect(data).toEqualData({id: 123, abcDef: 'xyz'}); $httpBackend.expectPATCH('/test/123', {test: {abc_def: 'xyz', id: 123, xyz: 'abc'}}).respond(200, {test: {id: 123, abc_def: 'xyz', xyz: 'abc', extra: 'test'}}); data.xyz = 'abc'; data.save(); $httpBackend.flush(); expect(data).toEqualData({id: 123, abcDef: 'xyz', xyz: 'abc', extra: 'test'}); }); it('create with default params should add parameter abc=1', function () { var promise, Resource, data, defaultParamsConfig = {}; $httpBackend.expectPOST('/test?abc=1', {test: {}}).respond(200, {test: {abc: 'xyz'}}); angular.copy(config, defaultParamsConfig); defaultParamsConfig.defaultParams = {abc: '1'}; Resource = factory(defaultParamsConfig); data = new Resource(); data.create(); $httpBackend.flush(); }); it('should be able to get resource and update it', function () { var promise, result; $httpBackend.expectGET('/test/123').respond(200, {test: {id: 123, abc: 'xyz', xyz: 'abcd'}}); expect(promise = Test.get(123)).toBeDefined(); promise.then(function (response) { result = response; }); $httpBackend.flush(); expect(result).toBeInstanceOf(Test); expect(result).toEqualData({id: 123, abc: 'xyz', xyz: 'abcd'}); $httpBackend.expectPUT('/test/123', {test: {id: 123, abc: 'xyz', xyz: 'abc'}}).respond(200, {test: {id: 123, abc: 'xyz', xyz: 'abc', extra: 'test'}}); result.xyz = 'abc'; result.update(); $httpBackend.flush(); // abc was originally set on the object so it should still be there after the update expect(result).toEqualData({id: 123, abc: 'xyz', xyz: 'abc', extra: 'test'}); }); it('update should handle 204 response', function () { var promise, result; $httpBackend.expectGET('/test/123').respond(200, {test: {id: 123, abc: 'xyz'}}); expect(promise = Test.get(123)).toBeDefined(); promise.then(function (response) { result = response; }); $httpBackend.flush(); expect(result).toBeInstanceOf(Test); expect(result).toEqualData({id: 123, abc: 'xyz'}); $httpBackend.expectPUT('/test/123', {test: {id: 123, abc: 'xyz', xyz: 'abc'}}).respond(204); result.xyz = 'abc'; result.update(); $httpBackend.flush(); expect(result).toEqualData({id: 123, abc: 'xyz', xyz: 'abc'}); }); it('should be able to delete instance returned from get', function () { var promise, result; $httpBackend.expectGET('/test/123').respond(200, {test: {id: 123, abc: 'xyz'}}); expect(promise = Test.get(123)).toBeDefined(); promise.then(function (response) { result = response; }); $httpBackend.flush(); expect(result).toBeInstanceOf(Test); expect(result).toEqualData({id: 123, abc: 'xyz'}); $httpBackend.expectDELETE('/test/123').respond(204); result.remove(); $httpBackend.flush(); }); it('delete with default params should add parameter abc=1', function () { var promise, Resource, data, defaultParamsConfig = {}; $httpBackend.expectDELETE('/test/123?abc=1').respond(204); angular.copy(config, defaultParamsConfig); defaultParamsConfig.defaultParams = {abc: '1'}; Resource = factory(defaultParamsConfig); data = new Resource(); data.id = 123; data.remove(); $httpBackend.flush(); }); it('should transform attributes on build', function() { var test = new Test({id: 123, abc_def: "T"}); expect(test).toEqualData({id: 123, abcDef: "T"}); }); it('should allow changing urls after first operation', function () { $httpBackend.expectGET('/test/123').respond(200, {test: {id: 123, abc: 'xyz'}}); Test.get(123); $httpBackend.flush(); $httpBackend.expectGET('/test2/123').respond(200, {test: {id: 123, abc: 'xyz'}}); Test.setUrl('/test2'); Test.get(123); $httpBackend.flush(); }); it('should support constructing with date properties', function () { var testDate = new Date(), test = new Test({id: 123, testDate: testDate}); expect(test.testDate).toBe(testDate); }); angular.forEach(['post', 'put', 'patch'], function (method) { it('should be able to ' + method + ' to arbitrary url', function () { var promise, result = {}; promise = Test['$' + method]('/xyz', {id: 123, abc: 'xyz', xyz: 'abc'}); $httpBackend['expect' + angular.uppercase(method)]('/xyz', {test: {id: 123, abc: 'xyz', xyz: 'abc'}}).respond(200, {test: {id: 123, abc: 'xyz', xyz: 'abc', extra: 'test'}}); promise.then(function (response) { result = response; }); $httpBackend.flush(); // abc was originally set on the object so it should still be there after the update expect(result).toEqualData({id: 123, abc: 'xyz', xyz: 'abc', extra: 'test'}); }); it('should be able to ' + method + ' instance to arbitrary url', function () { var test = new Test({id: 123, abc: 'xyz', xyz: 'abc'}); $httpBackend['expect' + angular.uppercase(method)]('/xyz', {test: {id: 123, abc: 'xyz', xyz: 'abc'}}).respond(200, {test: {id: 123, abc: 'xyz', xyz: 'abc', extra: 'test'}}); test['$' + method]('/xyz'); $httpBackend.flush(); // abc was originally set on the object so it should still be there after the update expect(test).toEqualData({id: 123, abc: 'xyz', xyz: 'abc', extra: 'test'}); }); }); it('should be able to $post an array of resources', function () { var data = [{id: 123, abc: 'xyz'}, {id: 124, abc: 'xyz'}]; $httpBackend['expectPOST']('/xyz', {tests: data} ).respond(200, {tests: data}); Test.$post('/xyz', data); $httpBackend.flush(); }); it('should not require query params on $delete', function () { $httpBackend.expectDELETE('/test').respond(200); Test.$delete('/test'); $httpBackend.flush(); }); it('should add query params passed to class $delete', function () { $httpBackend.expectDELETE('/test?a=1').respond(200); Test.$delete('/test', {a: 1}); $httpBackend.flush(); }); it('should add query params passed to instance $delete', function () { var data = new Test({abcDef: 'xyz'}); $httpBackend.expectDELETE('/test?a=1').respond(200); data.$delete('/test', {a: 1}); $httpBackend.flush(); }); }); describe('plural', function() { var $httpBackend, $rootScope, factory, PluralTest, pluralConfig = { url: '/pluralTest', name: 'singular', pluralName: 'plural' }; beforeEach(inject(function (_$httpBackend_, _$rootScope_, railsResourceFactory) { $httpBackend = _$httpBackend_; $rootScope = _$rootScope_; factory = railsResourceFactory; PluralTest = railsResourceFactory(pluralConfig); })); afterEach(function() { $httpBackend.verifyNoOutstandingExpectation(); $httpBackend.verifyNoOutstandingRequest(); }); it('query should return array of resource objects when result is an array', function () { var promise, result; $httpBackend.expectGET('/pluralTest').respond(200, {plural: [{abc: 'xyz'}, {xyz: 'abc'}]}); expect(promise = PluralTest.query()).toBeDefined(); promise.then(function (response) { result = response; }); $httpBackend.flush(); expect(angular.isArray(result)).toBe(true); angular.forEach(result, function (value) { expect(value).toBeInstanceOf(PluralTest); }); expect(result[0]).toEqualData({abc: 'xyz'}); expect(result[1]).toEqualData({xyz: 'abc'}); }); it('query should return empty array when result is empty array', function () { var promise, result; $httpBackend.expectGET('/pluralTest').respond(200, {plural: []}); expect(promise = PluralTest.query()).toBeDefined(); promise.then(function (response) { result = response; }); $httpBackend.flush(); expect(angular.isArray(result)).toBe(true); expect(result.length).toBe(0); }); }); describe('subclassing', function() { var $httpBackend, $rootScope, Book, CarManual, // generated CoffeeScript Code __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; beforeEach(inject(function (_$httpBackend_, _$rootScope_, RailsResource) { $httpBackend = _$httpBackend_; $rootScope = _$rootScope_; // generated CoffeeScript Code Book = (function(_super) { __extends(Book, _super); // @configure url: '/books', name: 'book' Book.configure({ url: '/books', name: 'book' }); Book.extend('RailsResourceSnapshotsMixin'); Book.extend({ bookProperty: 1}); function Book() { Book.__super__.constructor.apply(this, arguments); this.subclass = true; } return Book; })(RailsResource); CarManual = (function(_super) { __extends(CarManual, _super); CarManual.configure({ url: '/car_manuals', name: 'car_manual' }); function CarManual() { CarManual.__super__.constructor.apply(this, arguments); this.subclass = true; } return CarManual; })(Book); })); afterEach(function() { $httpBackend.verifyNoOutstandingExpectation(); $httpBackend.verifyNoOutstandingRequest(); }); it('get should return resource instance of subclass', function() { var promise, result; $httpBackend.expectGET('/car_manuals/123').respond(200, {car_manual: {id: 123, abc: 'xyz'}}); expect(promise = CarManual.get(123)).toBeDefined(); promise.then(function (response) { result = response; }); $httpBackend.flush(); expect(result).toBeInstanceOf(CarManual); expect(result).toEqualData({id: 123, abc: 'xyz', subclass: true}); }); it('should have configuration per-class', function() { var promise, result; $httpBackend.expectGET('/books/123').respond(200, {book: {id: 123, abc: 'xyz'}}); expect(promise = Book.get(123)).toBeDefined(); promise.then(function (response) { result = response; }); $httpBackend.flush(); expect(result).toBeInstanceOf(Book); expect(result).toEqualData({id: 123, abc: 'xyz', subclass: true}); }); it('should allow constructing subclasses with data', function () { var carManual = new CarManual({id: 1, name: 'Honda CR-V'}); expect(carManual.id).toBe(1); expect(carManual.name).toBe('Honda CR-V'); }); it('should have included properties on subclass', function () { expect(CarManual.bookProperty).toBe(1); expect(CarManual.prototype.snapshot).toBeDefined(); }); }); describe('mixins', function () { var railsResourceFactory; beforeEach(inject(function (_railsResourceFactory_) { railsResourceFactory = _railsResourceFactory_; })); it('should include extensions as part of initial configure', function () { var Resource = railsResourceFactory({name: 'test', url: '/test', extensions: ['snapshots']}); expect(Resource.prototype.snapshot).toBeDefined(); }); it('should only include extensions once', function () { var Resource = railsResourceFactory({name: 'test', url: '/test', extensions: ['snapshots', 'snapshots']}); expect(Resource.$mixins.length).toBe(2); // Snapshots extension includes additional mixin expect(Resource.prototype.snapshot).toBeDefined(); }); it('should only include module once', function () { var mixin = {test: 1}, Resource = railsResourceFactory({name: 'test', url: '/test'}); Resource.include(mixin); Resource.include(mixin); expect(Resource.$mixins.length).toBe(1); }); it('should include extensions as part of second configure call', function () { var Resource = railsResourceFactory({name: 'test', url: '/test'}); Resource.configure({extensions: ['snapshots']}); expect(Resource.prototype.snapshot).toBeDefined(); }); it('should throw an error if extension is not valid', function () { var Resource = railsResourceFactory({name: 'test', url: '/test'}); expect(function () { Resource.configure({extensions: ['invalid']}); }).toThrow(); }); it('should include object properties as class properties', function () { var Resource = railsResourceFactory({name: 'test', url: '/test'}); Resource.extend({ classMethod: function () {}, classProperty: 1 }); expect(Resource.classMethod).toBeDefined(); expect(Resource.classProperty).toBe(1); }); it('should include class properties', function () { function Mixin() {} Mixin.classMethod = function () {}; Mixin.classProperty = 1; var Resource = railsResourceFactory({name: 'test', url: '/test'}); Resource.extend(Mixin); expect(Resource.classMethod).toBe(Mixin.classMethod); expect(Resource.classProperty).toBe(Mixin.classProperty); }); it('should include instance properties', function () { function Mixin() {} Mixin.instanceMethod = function () {}; Mixin.instanceProperty = 1; var Resource = railsResourceFactory({name: 'test', url: '/test'}); Resource.include(Mixin); expect(Resource.prototype.instanceMethod).toBe(Mixin.instanceMethod); expect(Resource.prototype.instanceProperty).toBe(Mixin.instanceProperty); }); }); });