parent
30e893bf6c
commit
25ecdfb0b2
@ -1,291 +0,0 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
import {opApiModule, opServicesModule} from '../../../../angular-modules'; |
||||
import {HalLink} from './hal-link.service'; |
||||
import {HalRequestService} from '../hal-request/hal-request.service'; |
||||
|
||||
describe('HalLink service', () => { |
||||
var $httpBackend:ng.IHttpBackendService; |
||||
var $rootScope:any; |
||||
var HalLink:any; |
||||
var link:HalLink; |
||||
|
||||
beforeEach(angular.mock.module(opApiModule.name, opServicesModule.name)); |
||||
beforeEach(angular.mock.inject(function (_$httpBackend_:any, |
||||
_$rootScope_:any, |
||||
_HalLink_:any, |
||||
halRequest:HalRequestService) { |
||||
[$httpBackend, $rootScope, HalLink] = _.toArray(arguments); |
||||
halRequest.defaultHeaders.caching.enabled = false; |
||||
})); |
||||
|
||||
it('should exist', () => { |
||||
expect(HalLink).to.exist; |
||||
}); |
||||
|
||||
describe('when creating a HalLink from an empty object', () => { |
||||
beforeEach(() => { |
||||
link = HalLink.fromObject({}); |
||||
}); |
||||
|
||||
it('should have the "get" method as default', () => { |
||||
expect(link.method).to.eq('get'); |
||||
}); |
||||
|
||||
it('should have a null href', () => { |
||||
expect(link.href).to.be.null; |
||||
}); |
||||
|
||||
it('should not be templated', () => { |
||||
expect(link.templated).to.be.false; |
||||
}); |
||||
|
||||
it('should have an empty string as title', () => { |
||||
expect(link.title).to.equal(''); |
||||
}); |
||||
}); |
||||
|
||||
describe('when fetching a link that has a null href', () => { |
||||
beforeEach(() => { |
||||
link = new HalLink(); |
||||
link.href = null; |
||||
}); |
||||
|
||||
it('should return a promise that has null as its return value', () => { |
||||
expect(link.$fetch()).to.eventually.be.null; |
||||
$rootScope.$apply(); |
||||
}); |
||||
}); |
||||
|
||||
describe('when the method of the link is "delete"', () => { |
||||
beforeEach(() => { |
||||
link = HalLink.fromObject({ |
||||
href: 'home', |
||||
method: 'delete' |
||||
}); |
||||
}); |
||||
|
||||
it('should throw no error', () => { |
||||
expect(() => link.$fetch()).not.to.throw(Error); |
||||
}); |
||||
}); |
||||
|
||||
describe('when passing headers to $fetch', () => { |
||||
beforeEach(() => { |
||||
link = HalLink.fromObject({href: 'foobar'}); |
||||
link.$fetch({param: 'foo'}, {foo: 'bar'}); |
||||
}); |
||||
|
||||
it('should send the headers', () => { |
||||
$httpBackend.expectGET('foobar?param=foo', (headers:any) => headers.foo === 'bar').respond(200, {}); |
||||
$httpBackend.flush(); |
||||
}); |
||||
}); |
||||
|
||||
describe('when using the link', () => { |
||||
var response:any; |
||||
var result:any; |
||||
|
||||
beforeEach(() => { |
||||
link = HalLink.fromObject({ |
||||
href: '/api/link' |
||||
}); |
||||
response = { |
||||
_links: {}, |
||||
hello: 'world' |
||||
}; |
||||
|
||||
link.$fetch().then(val => result = val); |
||||
$httpBackend.expectGET('/api/link').respond(200, response); |
||||
$httpBackend.flush(); |
||||
}); |
||||
|
||||
it('should return a promise that returns the given value', () => { |
||||
expect(result.hello).to.eq(response.hello); |
||||
}); |
||||
|
||||
it('should return a HalResource', () => { |
||||
expect(result.$isHal).to.be.true; |
||||
}); |
||||
|
||||
it('should perform a GET request by default', () => { |
||||
link.$fetch(); |
||||
$httpBackend.expectGET('/api/link').respond(200, {}); |
||||
$httpBackend.flush(); |
||||
}); |
||||
|
||||
it('should send the provided data', () => { |
||||
const data = {hello: 'world'}; |
||||
link.method = 'post'; |
||||
|
||||
link.$fetch(data); |
||||
$httpBackend.expect('POST', '/api/link', data).respond(200, {}); |
||||
$httpBackend.flush(); |
||||
}); |
||||
|
||||
it('should perform a POST request', () => { |
||||
link.method = 'post'; |
||||
|
||||
link.$fetch(); |
||||
$httpBackend.expectPOST('/api/link').respond(200, {}); |
||||
$httpBackend.flush(); |
||||
}); |
||||
|
||||
it('should perform a PUT request', () => { |
||||
link.method = 'put'; |
||||
|
||||
link.$fetch(); |
||||
$httpBackend.expectPUT('/api/link').respond(200, {}); |
||||
$httpBackend.flush(); |
||||
}); |
||||
|
||||
it('should perform a PATCH request', () => { |
||||
link.method = 'patch'; |
||||
|
||||
link.$fetch(); |
||||
$httpBackend.expectPATCH('/api/link').respond(200, {}); |
||||
$httpBackend.flush(); |
||||
}); |
||||
|
||||
describe('when making the link callable', () => { |
||||
var func:any; |
||||
const runChecks = () => { |
||||
it('should return a function that fetches the data', () => { |
||||
func(); |
||||
|
||||
$httpBackend.expectPOST('foo').respond(200, {}); |
||||
$httpBackend.flush(); |
||||
}); |
||||
|
||||
it('should pass the params to $fetch', () => { |
||||
var $fetch = sinon.spy(link, '$fetch'); |
||||
func('hello'); |
||||
|
||||
expect($fetch.calledWith('hello')).to.be.true; |
||||
}); |
||||
|
||||
it('should have the href property of the link', () => { |
||||
expect(func.href).to.equal(link.href); |
||||
}); |
||||
|
||||
it('should have the title property of the link', () => { |
||||
expect(func.title).to.equal(link.title); |
||||
}); |
||||
|
||||
it('should have the method property of the link', () => { |
||||
expect(func.method).to.equal(link.method); |
||||
}); |
||||
|
||||
it('should have the templated property of the link', () => { |
||||
expect(func.templated).to.equal(link.templated); |
||||
}); |
||||
}; |
||||
|
||||
beforeEach(() => { |
||||
link.href = 'foo'; |
||||
link.title = 'title'; |
||||
link.method = 'post'; |
||||
link.templated = true; |
||||
}); |
||||
|
||||
describe('when using the instance method', () => { |
||||
beforeEach(() => { |
||||
func = link.$callable(); |
||||
}); |
||||
runChecks(); |
||||
}); |
||||
|
||||
describe('when using the static factory method', () => { |
||||
beforeEach(() => { |
||||
func = HalLink.callable(link); |
||||
link = func.$link; |
||||
}); |
||||
runChecks(); |
||||
}); |
||||
}); |
||||
|
||||
describe('when $preparing the link', () => { |
||||
var func:any; |
||||
|
||||
beforeEach(() => { |
||||
link.href = '/foo/bar/{user_id}'; |
||||
link.title = 'title'; |
||||
link.method = 'post'; |
||||
link.templated = true; |
||||
}); |
||||
|
||||
describe('when the link is NOT templated', () => { |
||||
beforeEach(() => { |
||||
link.templated = false; |
||||
}); |
||||
it('should raise an exception', () => { |
||||
expect(function() { |
||||
link.$prepare({}); |
||||
}).to.throw; |
||||
}); |
||||
}); |
||||
|
||||
describe('when the link is templated', () => { |
||||
beforeEach(() => { |
||||
func = link.$prepare({ user_id: '1234' }); |
||||
}); |
||||
|
||||
it('should return a function that fetches the data', () => { |
||||
func(); |
||||
|
||||
$httpBackend.expectPOST('/foo/bar/1234').respond(200, {}); |
||||
$httpBackend.flush(); |
||||
}); |
||||
|
||||
it('should pass the params to $fetch', () => { |
||||
var $fetch = sinon.spy(func.$link, '$fetch'); |
||||
func('hello'); |
||||
|
||||
expect($fetch.calledWith('hello')).to.be.true; |
||||
}); |
||||
|
||||
it('should have the untemplated href property', () => { |
||||
expect(func.href).to.equal('/foo/bar/1234'); |
||||
}); |
||||
|
||||
it('should have the title property of the link', () => { |
||||
expect(func.title).to.equal(link.title); |
||||
}); |
||||
|
||||
it('should have the method property of the link', () => { |
||||
expect(func.method).to.equal(link.method); |
||||
}); |
||||
|
||||
it('should not be templated', () => { |
||||
expect(func.templated).to.equal(false); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
@ -1,241 +0,0 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
import {opApiModule} from '../../../../angular-modules'; |
||||
import {HalRequestService} from './hal-request.service'; |
||||
import {HalResource} from '../hal-resources/hal-resource.service'; |
||||
import IPromise = angular.IPromise; |
||||
import IRootScopeService = angular.IRootScopeService; |
||||
import IHttpBackendService = angular.IHttpBackendService; |
||||
|
||||
describe('halRequest service', () => { |
||||
var $httpBackend:IHttpBackendService; |
||||
var $rootScope:IRootScopeService; |
||||
var halRequest:HalRequestService; |
||||
|
||||
beforeEach(angular.mock.module(opApiModule.name)); |
||||
beforeEach(angular.mock.inject(function (_$httpBackend_:any, _$rootScope_:any, _halRequest_:any) { |
||||
[$httpBackend, $rootScope, halRequest] = _.toArray(arguments); |
||||
})); |
||||
|
||||
it('should exist', () => { |
||||
expect(halRequest).to.exist; |
||||
}); |
||||
|
||||
afterEach(() => { |
||||
$rootScope.$apply(); |
||||
}); |
||||
|
||||
describe('when requesting a null href', () => { |
||||
var promise:any; |
||||
|
||||
beforeEach(() => { |
||||
promise = halRequest.request('get', ''); |
||||
}); |
||||
|
||||
it('should return a rejected promise', () => { |
||||
expect(promise).to.eventually.be.rejected; |
||||
}); |
||||
}); |
||||
|
||||
describe('when requesting the same GET resource multiple times', () => { |
||||
var headers:any; |
||||
|
||||
const testRequest = (prepare:any) => { |
||||
beforeEach(prepare); |
||||
|
||||
it('should perform requests according to the cache options', () => { |
||||
$httpBackend.expectGET('something').respond(200, '', (headers:any) => { |
||||
return headers.caching.enabled === true; |
||||
}); |
||||
|
||||
if (headers.caching && headers.caching.enabled === false) { |
||||
$httpBackend.expectGET('something').respond(200, ''); |
||||
} |
||||
}); |
||||
}; |
||||
const testMethods = () => { |
||||
describe('when using request()', () => { |
||||
testRequest(() => { |
||||
halRequest.request('get', 'something', {}, headers); |
||||
halRequest.request('get', 'something', {}, headers); |
||||
}); |
||||
}); |
||||
|
||||
describe('when using get()', () => { |
||||
testRequest(() => { |
||||
halRequest.get('something', {}, headers); |
||||
halRequest.get('something', {}, headers); |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
beforeEach(() => { |
||||
halRequest.defaultHeaders.caching = {enabled: true}; |
||||
headers = {}; |
||||
}); |
||||
|
||||
testMethods(); |
||||
|
||||
describe('when sending a no cache header', () => { |
||||
beforeEach(() => { |
||||
headers = {caching: {enabled: false}}; |
||||
}); |
||||
|
||||
testMethods(); |
||||
}); |
||||
}); |
||||
|
||||
describe('when requesting data', () => { |
||||
var promise:IPromise<HalResource>; |
||||
var method:string; |
||||
var data:any; |
||||
var expectedData:any; |
||||
var headers = {Accept: 'foo', caching: {enabled: false}}; |
||||
|
||||
const methods = ['get', 'put', 'post', 'patch', 'delete']; |
||||
const respond = (status:any, response:any) => { |
||||
$httpBackend |
||||
.expect(method.toUpperCase(), 'href', expectedData, (headers:any) => { |
||||
return headers.Accept === 'foo'; |
||||
}) |
||||
.respond(status, response); |
||||
|
||||
$httpBackend.flush(); |
||||
}; |
||||
const runTests = () => { |
||||
describe('when no error occurs', () => { |
||||
beforeEach(() => respond(200, {})); |
||||
it('should return a HalResource', () => { |
||||
expect(promise).to.eventually.be.an.instanceOf(HalResource); |
||||
}); |
||||
}); |
||||
|
||||
describe('when an error occurs', () => { |
||||
beforeEach(() => respond(400, {})); |
||||
|
||||
it('should be rejected with an instance of HalResource', () => { |
||||
expect(promise).to.eventually.be.rejectedWith(HalResource); |
||||
}); |
||||
}); |
||||
|
||||
describe('when the server does not respond with a result', () => { |
||||
beforeEach(() => respond(200, null)); |
||||
|
||||
it('should return nothing as well', () => { |
||||
expect(promise).to.eventually.be.null; |
||||
}); |
||||
}); |
||||
}; |
||||
const runRequests = (cb:any) => { |
||||
const callback = () => { |
||||
if (method === 'get') { |
||||
data = null; |
||||
expectedData = null; |
||||
} |
||||
cb(); |
||||
}; |
||||
methods.forEach(requestMethod => { |
||||
describe(`when performing a ${requestMethod} request`, () => { |
||||
beforeEach(() => { |
||||
method = requestMethod; |
||||
}); |
||||
|
||||
describe('when sending no data with the request', () => { |
||||
beforeEach(() => { |
||||
data = void 0; |
||||
expectedData = void 0; |
||||
|
||||
if (method === 'post') { |
||||
expectedData = {}; |
||||
} |
||||
|
||||
callback(); |
||||
}); |
||||
|
||||
runTests(); |
||||
}); |
||||
|
||||
describe('when sending data with the request', () => { |
||||
beforeEach(() => { |
||||
data = {foo: 'bar'}; |
||||
expectedData = data; |
||||
callback(); |
||||
}); |
||||
|
||||
runTests(); |
||||
}); |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
describe('when calling the http methods of the service', () => { |
||||
runRequests(() => { |
||||
promise = (halRequest as any)[method]('href', data, headers); |
||||
}); |
||||
}); |
||||
|
||||
describe('when calling request()', () => { |
||||
runRequests(() => { |
||||
promise = halRequest.request(method, 'href', data, headers); |
||||
}); |
||||
}); |
||||
|
||||
describe('when requesting a GET resource with parameters', () => { |
||||
const params = {foo: 'bar'}; |
||||
|
||||
beforeEach(() => { |
||||
promise = halRequest.get('href', params); |
||||
}); |
||||
|
||||
it('should append the parameters at the end of the requested url', () => { |
||||
$httpBackend.expectGET('href?foo=bar').respond(200, {}); |
||||
$httpBackend.flush(); |
||||
}); |
||||
}); |
||||
|
||||
describe('#getAllPaginated', () => { |
||||
const params = {}; |
||||
let promise:any; |
||||
|
||||
beforeEach(() => { |
||||
promise = halRequest.getAllPaginated('href', 25, params); |
||||
|
||||
$httpBackend.expectGET('href?offset=1').respond(200, { count: 12, total: 25 }); |
||||
$httpBackend.expectGET('href?offset=2').respond(200, { count: 12, total: 25 }); |
||||
$httpBackend.expectGET('href?offset=3').respond(200, { count: 1, total: 25 }); |
||||
}); |
||||
|
||||
it('should resolve with three results', () => { |
||||
expect(promise).to.eventually.be.fulfilled.then(allResults => { |
||||
expect(allResults.length).to.eq(3); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
@ -1,114 +0,0 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
import {opApiModule} from '../../../../angular-modules'; |
||||
import IPromise = angular.IPromise; |
||||
import IRootScopeService = angular.IRootScopeService; |
||||
import IHttpBackendService = angular.IHttpBackendService; |
||||
import {RelationsDmService} from './relations-dm.service'; |
||||
import {buildApiV3Filter} from '../api-v3-filter-builder'; |
||||
|
||||
describe('relationsDm service', () => { |
||||
var $httpBackend:IHttpBackendService; |
||||
var relationsDm:RelationsDmService; |
||||
var $rootScope:ng.IRootScopeService; |
||||
|
||||
beforeEach(angular.mock.module(opApiModule.name)); |
||||
beforeEach(angular.mock.inject(function (_relationsDm_:RelationsDmService, _$rootScope_:ng.IRootScopeService, _$httpBackend_:ng.IHttpBackendService) { |
||||
$httpBackend = _$httpBackend_; |
||||
relationsDm = _relationsDm_; |
||||
$rootScope = _$rootScope_; |
||||
})); |
||||
|
||||
it('should exist', () => { |
||||
expect(relationsDm).to.exist; |
||||
}); |
||||
|
||||
afterEach(() => { |
||||
$rootScope.$apply(); |
||||
}); |
||||
|
||||
function filterString(ids:string[]) { |
||||
let filterString = encodeURI(buildApiV3Filter('involved', '=', ids).toJson()); |
||||
// Angular extends on encodeURI to unescape some values..
|
||||
// https://github.com/angular/angular.js/blob/v1.5.x/src/Angular.js
|
||||
filterString = filterString.replace(/=/gi, '%3D'); |
||||
return '?filters=' + filterString; |
||||
} |
||||
|
||||
describe('#loadInvolved', () => { |
||||
let promise:ng.IPromise<any>; |
||||
let ids:string[]; |
||||
|
||||
describe('when requesting some IDs', () => { |
||||
beforeEach(() => { |
||||
ids = ['1', '2', '3']; |
||||
promise = relationsDm.loadInvolved(ids); |
||||
}); |
||||
|
||||
it('should append the parameters at the end of the requested url', () => { |
||||
$httpBackend.expectGET('/api/v3/relations' + filterString(ids)).respond(200, { elements: ['foo'] }); |
||||
$httpBackend.flush(); |
||||
|
||||
expect(promise).to.eventually.be.fulfilled.then((relations) => { |
||||
expect(relations).to.deep.equal(['foo']); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('when requesting with an invalid IDs', () => { |
||||
beforeEach(() => { |
||||
ids = ['1', 'foo']; |
||||
promise = relationsDm.loadInvolved(ids); |
||||
}); |
||||
|
||||
it('should append the parameters at the end of the requested url', () => { |
||||
$httpBackend.expectGET('/api/v3/relations' + filterString(['1'])).respond(200, { elements: ['foo'] }); |
||||
$httpBackend.flush(); |
||||
|
||||
expect(promise).to.eventually.be.fulfilled.then((relations) => { |
||||
expect(relations).to.deep.equal(['foo']); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('when requesting with no valid IDs', () => { |
||||
beforeEach(() => { |
||||
ids = ['foo']; |
||||
promise = relationsDm.loadInvolved(ids); |
||||
}); |
||||
|
||||
it('should append the parameters at the end of the requested url', () => { |
||||
expect($httpBackend.flush.bind($httpBackend)).to.throw('No pending request to flush !'); |
||||
expect(promise).to.eventually.be.fulfilled.then((relations) => { |
||||
expect(relations).to.be.empty; |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
@ -1,23 +0,0 @@ |
||||
export interface ChangesetProxy { |
||||
changesetReset():void; |
||||
|
||||
changesetPersist():void; |
||||
} |
||||
|
||||
export function createChangeSetProxy<T>(target:T):T & ChangesetProxy { |
||||
const proxy = {} as any; |
||||
Object.setPrototypeOf(proxy, target); |
||||
|
||||
proxy.changesetReset = () => { |
||||
_.forOwn(proxy, (value, key) => delete proxy[key]); |
||||
}; |
||||
|
||||
proxy.changesetPersist = () => { |
||||
_.forOwn(proxy, (value, key) => { |
||||
(target as any)[key] = value; |
||||
delete proxy[key]; |
||||
}); |
||||
}; |
||||
|
||||
return proxy; |
||||
} |
@ -1,109 +0,0 @@ |
||||
// -- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
// ++
|
||||
|
||||
import {opApiModule} from '../../../../angular-modules'; |
||||
import {HalResourceFactoryService} from './hal-resource-factory.service'; |
||||
import {HalResource} from '../hal-resources/hal-resource.service'; |
||||
|
||||
describe('halResourceFactory', () => { |
||||
var halResourceFactory:HalResourceFactoryService; |
||||
var resource:HalResource; |
||||
|
||||
class OtherResource extends HalResource { |
||||
} |
||||
|
||||
beforeEach(angular.mock.module(opApiModule.name)); |
||||
beforeEach(angular.mock.module(opApiModule.name, ($provide:any) => { |
||||
$provide.value('OtherResource', OtherResource); |
||||
})); |
||||
beforeEach(angular.mock.inject(function (_halResourceFactory_:any) { |
||||
[halResourceFactory] = _.toArray(arguments); |
||||
})); |
||||
|
||||
it('should exist', () => { |
||||
expect(halResourceFactory).to.exist; |
||||
}); |
||||
|
||||
it('should have HalResource as its default class', () => { |
||||
expect(halResourceFactory.defaultClass).to.equal(HalResource); |
||||
}); |
||||
|
||||
describe('when no resource type is configured', () => { |
||||
describe('when creating a resource', () => { |
||||
beforeEach(() => { |
||||
resource = halResourceFactory.createHalResource({}); |
||||
}); |
||||
|
||||
it('should create an instance of the default type', () => { |
||||
expect(resource).to.be.an.instanceOf(halResourceFactory.defaultClass); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('when a resource type is configured', () => { |
||||
beforeEach(() => { |
||||
halResourceFactory.setResourceType('Other', OtherResource); |
||||
}); |
||||
|
||||
describe('when creating a resource of that type', () => { |
||||
beforeEach(() => { |
||||
resource = halResourceFactory.createHalResource({_type: 'Other'}); |
||||
}); |
||||
|
||||
it('should be an instance of the configured type', () => { |
||||
expect(resource).to.be.an.instanceOf(OtherResource); |
||||
}); |
||||
}); |
||||
|
||||
describe('when adding attribute configuration for that type', () => { |
||||
beforeEach(() => { |
||||
halResourceFactory.setResourceTypeAttributes('Other', { |
||||
attr: 'Other' |
||||
}); |
||||
resource = halResourceFactory.createLinkedHalResource({}, 'Other', 'attr'); |
||||
}); |
||||
|
||||
it('should be an instance of the configured attr type', () => { |
||||
expect(resource).to.be.an.instanceOf(OtherResource); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('when adding attr type configuration to for a non configured type', () => { |
||||
beforeEach(() => { |
||||
halResourceFactory.setResourceTypeAttributes('NonExistent', { |
||||
attr: 'NonExistent' |
||||
}); |
||||
resource = halResourceFactory.createLinkedHalResource({}, 'NonExistent', 'attr'); |
||||
}); |
||||
|
||||
it('should create a resource from the default tpye', () => { |
||||
expect(resource).to.be.an.instanceOf(halResourceFactory.defaultClass); |
||||
}); |
||||
}); |
||||
}); |
@ -1,92 +0,0 @@ |
||||
// -- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
// ++
|
||||
|
||||
import {opApiModule} from '../../../../angular-modules'; |
||||
import {HalResourceTypesService} from './hal-resource-types.service'; |
||||
|
||||
function halResourceTypesConfig(halResourceTypes:HalResourceTypesService) { |
||||
halResourceTypes.setResourceTypeConfig({ |
||||
WorkPackage: { |
||||
className: 'WorkPackageResource', |
||||
attrTypes: { |
||||
parent: 'WorkPackage', |
||||
ancestors: 'WorkPackage', |
||||
children: 'WorkPackage', |
||||
relations: 'Relation', |
||||
schema: 'Schema', |
||||
type: 'Type' |
||||
} |
||||
}, |
||||
Activity: { |
||||
user: 'User' |
||||
}, |
||||
'Activity::Comment': { |
||||
user: 'User' |
||||
}, |
||||
'Activity::Revision': { |
||||
user: 'User' |
||||
}, |
||||
Relation: { |
||||
className: 'RelationResource', |
||||
attrTypes: { |
||||
from: 'WorkPackage', |
||||
to: 'WorkPackage' |
||||
} |
||||
}, |
||||
Schema: 'SchemaResource', |
||||
Type: 'TypeResource', |
||||
SchemaDependency: 'SchemaDependencyResource', |
||||
Error: 'ErrorResource', |
||||
User: 'UserResource', |
||||
Collection: 'CollectionResource', |
||||
WorkPackageCollection: 'WorkPackageCollectionResource', |
||||
Query: { |
||||
className: 'QueryResource', |
||||
attrTypes: { |
||||
filters: 'QueryFilterInstance' |
||||
} |
||||
}, |
||||
Form: 'FormResource', |
||||
QueryFilterInstance: { |
||||
className: 'QueryFilterInstanceResource' , |
||||
attrTypes: { |
||||
schema: 'QueryFilterInstanceSchema', |
||||
filter: 'QueryFilter', |
||||
operator: 'QueryOperator' |
||||
} |
||||
}, |
||||
QueryFilterInstanceSchema: 'QueryFilterInstanceSchemaResource', |
||||
QueryFilter: 'QueryFilterResource', |
||||
Root: 'RootResource', |
||||
QueryOperator: 'QueryOperatorResource', |
||||
HelpText: 'HelpTextResource', |
||||
CustomAction: 'CustomActionResource' |
||||
}); |
||||
} |
||||
|
||||
opApiModule.run(halResourceTypesConfig); |
@ -1,133 +0,0 @@ |
||||
// -- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
// ++
|
||||
|
||||
import {opApiModule} from '../../../../angular-modules'; |
||||
import {HalResourceTypesService} from './hal-resource-types.service'; |
||||
import {HalResourceFactoryService} from '../hal-resource-factory/hal-resource-factory.service'; |
||||
|
||||
describe('halResourceTypes service', () => { |
||||
var halResourceTypes:HalResourceTypesService; |
||||
var halResourceFactory:HalResourceFactoryService; |
||||
var config:any; |
||||
var compareCls:typeof HalResource; |
||||
|
||||
class HalResource { |
||||
} |
||||
class OtherResource { |
||||
} |
||||
class FooResource { |
||||
} |
||||
|
||||
beforeEach(angular.mock.module(opApiModule.name, ($provide:any) => { |
||||
$provide.value('HalResource', HalResource); |
||||
$provide.value('OtherResource', OtherResource); |
||||
$provide.value('FooResource', FooResource); |
||||
})); |
||||
|
||||
beforeEach(angular.mock.inject(function (_halResourceTypes_:any, _halResourceFactory_:any) { |
||||
[halResourceTypes, halResourceFactory] = _.toArray(arguments); |
||||
})); |
||||
|
||||
const expectResourceClassAdded = () => { |
||||
it('should add the respective class object to the storage', () => { |
||||
const resource = halResourceFactory.createHalResource({_type: 'Other'}); |
||||
expect(resource).to.be.an.instanceOf(compareCls); |
||||
}); |
||||
}; |
||||
|
||||
const expectAttributeClassAdded = () => { |
||||
it('should add the attribute type config to the storage', () => { |
||||
const resource = halResourceFactory.createLinkedHalResource({}, 'Other', 'attr'); |
||||
expect(resource).to.be.an.instanceOf(compareCls); |
||||
}); |
||||
}; |
||||
|
||||
it('should exist', () => { |
||||
expect(halResourceTypes).to.exist; |
||||
}); |
||||
|
||||
describe('when configuring the type with class and attributes', () => { |
||||
beforeEach(() => { |
||||
compareCls = OtherResource; |
||||
config = { |
||||
Other: { |
||||
className: 'OtherResource', |
||||
attrTypes: { |
||||
attr: 'Other' |
||||
} |
||||
} |
||||
}; |
||||
halResourceTypes.setResourceTypeConfig(config); |
||||
}); |
||||
|
||||
expectResourceClassAdded(); |
||||
expectAttributeClassAdded(); |
||||
}); |
||||
|
||||
describe('when configuring the type with the class name as value', () => { |
||||
beforeEach(() => { |
||||
compareCls = OtherResource; |
||||
config = { |
||||
Other: 'OtherResource' |
||||
}; |
||||
halResourceTypes.setResourceTypeConfig(config); |
||||
}); |
||||
|
||||
expectResourceClassAdded(); |
||||
}); |
||||
|
||||
describe('when configuring the type with only the attribute types', () => { |
||||
beforeEach(() => { |
||||
compareCls = halResourceFactory.defaultClass; |
||||
config = { |
||||
Other: { |
||||
attr: 'Other' |
||||
} |
||||
}; |
||||
halResourceTypes.setResourceTypeConfig(config); |
||||
}); |
||||
|
||||
expectResourceClassAdded(); |
||||
expectAttributeClassAdded(); |
||||
}); |
||||
|
||||
describe('when an attribute has a type, that defined later in the config', () => { |
||||
beforeEach(() => { |
||||
compareCls = FooResource; |
||||
config = { |
||||
Other: { |
||||
attr: 'Foo' |
||||
}, |
||||
Foo: 'FooResource', |
||||
}; |
||||
halResourceTypes.setResourceTypeConfig(config); |
||||
}); |
||||
|
||||
expectAttributeClassAdded(); |
||||
}); |
||||
}); |
@ -1,85 +0,0 @@ |
||||
// -- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
// ++
|
||||
|
||||
import {opApiModule} from '../../../../angular-modules'; |
||||
import {HalResourceFactoryService} from '../hal-resource-factory/hal-resource-factory.service'; |
||||
|
||||
export class HalResourceTypesService { |
||||
constructor(protected $injector:ng.auto.IInjectorService, |
||||
protected halResourceFactory:HalResourceFactoryService, |
||||
HalResource:any) { |
||||
halResourceFactory.defaultClass = HalResource; |
||||
} |
||||
|
||||
public setResourceTypeConfig(config:any) { |
||||
const types = Object.keys(config).map(typeName => { |
||||
const value = config[typeName]; |
||||
const result = { |
||||
typeName: typeName, |
||||
className: value.className || this.getClassName(this.halResourceFactory.defaultClass), |
||||
attrTypes: value.attrTypes || {} |
||||
}; |
||||
|
||||
if (angular.isString(value)) { |
||||
result.className = value; |
||||
} |
||||
|
||||
if (!value.className && angular.isObject(value)) { |
||||
result.attrTypes = value; |
||||
} |
||||
|
||||
return result; |
||||
}); |
||||
|
||||
types.forEach(typeConfig => { |
||||
this.halResourceFactory |
||||
.setResourceType(typeConfig.typeName, this.$injector.get(typeConfig.className) as any); |
||||
}); |
||||
|
||||
types |
||||
.forEach(typeConfig => { |
||||
this.halResourceFactory.setResourceTypeAttributes(typeConfig.typeName, typeConfig.attrTypes); |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* IE11 has no support for <Function>.name, thus polyfill the actual class name |
||||
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name
|
||||
* @param cls |
||||
*/ |
||||
protected getClassName(cls:Function) { |
||||
if (cls.hasOwnProperty('name')) { |
||||
return cls.name; |
||||
} |
||||
|
||||
const matches = cls.toString().match(/^function\s*([^\s(]+)/) as any[]; |
||||
return matches[1]; |
||||
} |
||||
} |
||||
|
||||
opApiModule.service('halResourceTypes', HalResourceTypesService); |
@ -1,74 +0,0 @@ |
||||
// -- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
// ++
|
||||
|
||||
import {opApiModule, opServicesModule} from '../../../../angular-modules'; |
||||
import {AttachmentCollectionResource} from './attachment-collection-resource.service'; |
||||
import {OpenProjectFileUploadService} from '../../op-file-upload/op-file-upload.service'; |
||||
|
||||
describe('AttachmentCollectionResource service', () => { |
||||
var AttachmentCollectionResource:any; |
||||
var opFileUpload: OpenProjectFileUploadService; |
||||
|
||||
beforeEach(angular.mock.module( |
||||
opApiModule.name, |
||||
opServicesModule.name |
||||
)); |
||||
beforeEach(angular.mock.inject(function (_AttachmentCollectionResource_:any, |
||||
_opFileUpload_:any) { |
||||
[AttachmentCollectionResource, opFileUpload] = _.toArray(arguments); |
||||
})); |
||||
|
||||
it('should exist', () => { |
||||
expect(AttachmentCollectionResource).to.exist; |
||||
}); |
||||
|
||||
describe('when creating an attachment collection', () => { |
||||
var collection: AttachmentCollectionResource; |
||||
|
||||
beforeEach(() => { |
||||
collection = new AttachmentCollectionResource({ |
||||
_links: {self: {href: 'attachments'}} |
||||
}, true); |
||||
}); |
||||
|
||||
describe('when using upload()', () => { |
||||
var uploadStub: sinon.SinonStub; |
||||
var params:any; |
||||
|
||||
beforeEach(() => { |
||||
params = [{}, {}]; |
||||
uploadStub = sinon.stub(opFileUpload, 'upload'); |
||||
collection.upload((params)); |
||||
}); |
||||
|
||||
it('should upload the files as expected', () => { |
||||
expect(uploadStub.calledWith(collection.$href, params)).to.be.true; |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
@ -1,60 +0,0 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
import {opApiModule} from '../../../../angular-modules'; |
||||
import {CollectionResource} from './collection-resource.service'; |
||||
import {HalResource} from './hal-resource.service'; |
||||
import { |
||||
OpenProjectFileUploadService, |
||||
UploadFile, UploadResult |
||||
} from '../../op-file-upload/op-file-upload.service'; |
||||
import IPromise = angular.IPromise; |
||||
|
||||
var opFileUpload: OpenProjectFileUploadService; |
||||
|
||||
export class AttachmentCollectionResource extends CollectionResource { |
||||
/** |
||||
* Upload the given files to the $href property of this resource. |
||||
*/ |
||||
public upload(files: UploadFile[]): UploadResult { |
||||
return opFileUpload.upload(this.$href as string, files); |
||||
} |
||||
} |
||||
|
||||
export interface AttachmentCollectionResourceInterface extends AttachmentCollectionResource { |
||||
elements: HalResource[]; |
||||
} |
||||
|
||||
function attachmentCollectionResourceService(...args:any[]) { |
||||
[opFileUpload] = args; |
||||
return AttachmentCollectionResource; |
||||
} |
||||
|
||||
attachmentCollectionResourceService.$inject = ['opFileUpload']; |
||||
|
||||
opApiModule.factory('AttachmentCollectionResource', attachmentCollectionResourceService); |
@ -1,76 +0,0 @@ |
||||
// -- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
// ++
|
||||
|
||||
import {opApiModule, opServicesModule} from '../../../../angular-modules'; |
||||
import IQService = angular.IQService; |
||||
import IRootScopeService = angular.IRootScopeService; |
||||
|
||||
describe('CollectionResource service', () => { |
||||
var $q:any; |
||||
var $rootScope:any; |
||||
var CollectionResource:any; |
||||
|
||||
var source:any; |
||||
var collection:any; |
||||
|
||||
beforeEach(angular.mock.module(opApiModule.name, opServicesModule.name)); |
||||
beforeEach(angular.mock.inject(function (_$q_:any, _$rootScope_:any, _CollectionResource_:any) { |
||||
[$q, $rootScope, CollectionResource] = _.toArray(arguments); |
||||
})); |
||||
|
||||
function createCollection() { |
||||
source = source || {}; |
||||
collection = new CollectionResource(source); |
||||
} |
||||
|
||||
it('should exist', () => { |
||||
expect(CollectionResource).to.exist; |
||||
}); |
||||
|
||||
describe('when using updateElements', () => { |
||||
var elements:any; |
||||
var result:any; |
||||
|
||||
beforeEach(() => { |
||||
createCollection(); |
||||
elements = [{}, {}]; |
||||
sinon.stub(collection, '$load').returns($q.when({elements})); |
||||
|
||||
result = collection.updateElements(); |
||||
$rootScope.$apply(); |
||||
}); |
||||
|
||||
it('should set the elements of the resource so the new value', () => { |
||||
expect(collection.elements).to.equal(elements); |
||||
}); |
||||
|
||||
it('should return a promise with the elements as the result', () => { |
||||
expect(result).to.eventually.equal(elements); |
||||
}); |
||||
}); |
||||
}); |
@ -1,55 +0,0 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
import {opApiModule} from '../../../../angular-modules'; |
||||
import {HalResource} from './hal-resource.service'; |
||||
import {WorkPackageResourceInterface} from 'core-components/api/api-v3/hal-resources/work-package-resource.service'; |
||||
|
||||
interface CustomActionResourceEmbedded { |
||||
} |
||||
|
||||
export interface CustomActionResourceLinks extends CustomActionResourceEmbedded { |
||||
self():ng.IPromise<CustomActionResourceInterface>; |
||||
executeImmediately(payload:any):ng.IPromise<WorkPackageResourceInterface>; |
||||
} |
||||
|
||||
export class CustomActionResource extends HalResource { |
||||
public $embedded:CustomActionResourceEmbedded; |
||||
public id:number; |
||||
public name:string; |
||||
public description:string; |
||||
} |
||||
|
||||
function customActionResource() { |
||||
return CustomActionResource; |
||||
} |
||||
|
||||
export interface CustomActionResourceInterface extends CustomActionResourceLinks, CustomActionResourceEmbedded, CustomActionResource { |
||||
} |
||||
|
||||
opApiModule.factory('CustomActionResource', customActionResource); |
@ -1,726 +0,0 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
import {opApiModule, opServicesModule} from '../../../../angular-modules'; |
||||
import {HalResource} from './hal-resource.service'; |
||||
import {HalResourceFactoryService} from '../hal-resource-factory/hal-resource-factory.service'; |
||||
import {HalRequestService} from '../hal-request/hal-request.service'; |
||||
import {HalLinkInterface} from './../hal-link/hal-link.service'; |
||||
|
||||
describe('HalResource service', () => { |
||||
var $httpBackend:ng.IHttpBackendService; |
||||
var halResourceFactory:HalResourceFactoryService; |
||||
var resource:any; |
||||
var source:any; |
||||
|
||||
class OtherResource extends HalResource { |
||||
} |
||||
|
||||
beforeEach(angular.mock.module(opApiModule.name, opServicesModule.name, ($provide:ng.auto.IProvideService) => { |
||||
$provide.value('OtherResource', OtherResource); |
||||
})); |
||||
beforeEach(angular.mock.inject(function (_$httpBackend_:any, |
||||
_halResourceFactory_:any, |
||||
halRequest:HalRequestService) { |
||||
[$httpBackend, halResourceFactory] = _.toArray(arguments); |
||||
halRequest.defaultHeaders.caching.enabled = false; |
||||
})); |
||||
|
||||
it('should exist', () => { |
||||
expect(HalResource).to.exist; |
||||
}); |
||||
|
||||
it('should be instantiable using a default object', () => { |
||||
expect(new HalResource().$href).to.equal(null); |
||||
}); |
||||
|
||||
describe('when updating a loaded resource using `$update()`', () => { |
||||
beforeEach(() => { |
||||
source = { |
||||
_links: { |
||||
self: { |
||||
href: 'hello' |
||||
} |
||||
} |
||||
}; |
||||
resource = new HalResource(source); |
||||
resource.$update(); |
||||
}); |
||||
|
||||
it('should perform a no-cache request', () => { |
||||
const expectHeaders = (headers:any) => headers.caching.enabled === false; |
||||
$httpBackend.expectGET('hello', expectHeaders).respond(200, {}); |
||||
$httpBackend.flush(); |
||||
}); |
||||
}); |
||||
|
||||
describe('when creating a resource using the create factory method', () => { |
||||
describe('when there is no type configuration', () => { |
||||
beforeEach(() => { |
||||
source = {_embedded: {}}; |
||||
resource = HalResource.create(source); |
||||
}); |
||||
|
||||
it('should be an instance of HalResource', () => { |
||||
expect(resource).to.be.an.instanceOf(HalResource); |
||||
}); |
||||
}); |
||||
|
||||
describe('when the type is configured', () => { |
||||
beforeEach(() => { |
||||
source = { |
||||
_type: 'Other', |
||||
_links: { |
||||
someResource: { |
||||
href: 'foo' |
||||
} |
||||
} |
||||
}; |
||||
|
||||
halResourceFactory.setResourceType('Other', OtherResource); |
||||
halResourceFactory.setResourceTypeAttributes('Other', { |
||||
someResource: 'Other' |
||||
}); |
||||
|
||||
resource = HalResource.create(source); |
||||
}); |
||||
|
||||
it('should be an instance of that type', () => { |
||||
expect(resource).to.be.an.instanceOf(OtherResource); |
||||
}); |
||||
|
||||
it('should have an attribute that is of the configured instance', () => { |
||||
expect(resource.someResource).to.be.an.instanceOf(OtherResource); |
||||
}); |
||||
|
||||
it('should not be loaded', () => { |
||||
expect(resource.someResource.$loaded).to.be.false; |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('when after generating the lazy object', () => { |
||||
var linkFn = sinon.spy(); |
||||
var embeddedFn = sinon.spy(); |
||||
|
||||
beforeEach(() => { |
||||
resource = new HalResource({ |
||||
_links: { |
||||
get link() { |
||||
linkFn(); |
||||
return {}; |
||||
} |
||||
}, |
||||
_embedded: { |
||||
get resource() { |
||||
embeddedFn(); |
||||
return {}; |
||||
} |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
it('should not have touched the source links initially', () => { |
||||
expect(linkFn.called).to.be.false; |
||||
}); |
||||
|
||||
it('should not have touched the embedded elements of the source initially', () => { |
||||
expect(embeddedFn.called).to.be.false; |
||||
}); |
||||
|
||||
it('should use the source link only once when called', () => { |
||||
resource.link; |
||||
resource.link; |
||||
expect(linkFn.calledOnce).to.be.true; |
||||
}); |
||||
|
||||
it('should use the source embedded only once when called', () => { |
||||
resource.resource; |
||||
resource.resource; |
||||
expect(embeddedFn.calledOnce).to.be.true; |
||||
}); |
||||
}); |
||||
|
||||
describe('when the source has properties, the resource', () => { |
||||
beforeEach(() => { |
||||
source = { |
||||
_links: {}, |
||||
_embedded: {}, |
||||
property: 'foo', |
||||
obj: { |
||||
foo: 'bar' |
||||
} |
||||
}; |
||||
resource = new HalResource(source); |
||||
}); |
||||
|
||||
it('should have the same properties', () => { |
||||
expect(resource.property).to.exist; |
||||
expect(resource.obj).to.exist; |
||||
}); |
||||
|
||||
it('should have properties with equal values', () => { |
||||
expect(resource.property).to.eq(source.property); |
||||
expect(resource.obj).to.eql(source.obj); |
||||
}); |
||||
|
||||
it('should not have the _links property', () => { |
||||
expect(resource._links).to.not.exist; |
||||
}); |
||||
|
||||
it('should not have the _embedded property', () => { |
||||
expect(resource._embedded).to.not.exist; |
||||
}); |
||||
|
||||
it('should have enumerable properties', () => { |
||||
expect(resource.propertyIsEnumerable('property')).to.be.true; |
||||
}); |
||||
|
||||
describe('when a property is changed', () => { |
||||
beforeEach(() => { |
||||
resource.property = 'carrot'; |
||||
}); |
||||
|
||||
it('should change the property of the source', () => { |
||||
expect(resource.$source.property).to.eq('carrot'); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('when creating a resource from a source with a self link', () => { |
||||
beforeEach(() => { |
||||
source = { |
||||
_links: { |
||||
self: { |
||||
href: '/api/hello', |
||||
title: 'some title' |
||||
} |
||||
} |
||||
}; |
||||
resource = new HalResource(source); |
||||
}); |
||||
|
||||
it('should have a name attribute that is equal to the title of the self link', () => { |
||||
expect(resource.name).to.eq('some title'); |
||||
}); |
||||
|
||||
it('should have a writable name attribute', () => { |
||||
resource.name = 'some name'; |
||||
expect(resource.name).to.eq('some name'); |
||||
}); |
||||
|
||||
it('should have a href property that is the same as the self href', () => { |
||||
expect(resource.href).to.eq(resource.$links.self.$link.href); |
||||
}); |
||||
|
||||
it('should have a href property that is equal to the source href', () => { |
||||
expect(resource.href).to.eq(source._links.self.href); |
||||
}); |
||||
|
||||
it('should not have a self property', () => { |
||||
expect(resource.self).not.to.exist; |
||||
}); |
||||
}); |
||||
|
||||
describe('when setting a property that is a resource to null', () => { |
||||
beforeEach(() => { |
||||
source = { |
||||
_links: { |
||||
resource: { |
||||
method: 'get', |
||||
href: 'resource/1' |
||||
} |
||||
} |
||||
}; |
||||
resource = new HalResource(source); |
||||
resource.resource = null; |
||||
}); |
||||
|
||||
it('should be null', () => { |
||||
expect(resource.resource).to.be.null; |
||||
}); |
||||
|
||||
it('should set the respective link href to null', () => { |
||||
expect(resource.$source._links.resource.href).to.be.null; |
||||
}); |
||||
}); |
||||
|
||||
describe('when a property that is a resource has a null href', () => { |
||||
beforeEach(() => { |
||||
source = { |
||||
_links: { |
||||
property: { |
||||
href: null |
||||
} |
||||
} |
||||
}; |
||||
resource = new HalResource(source); |
||||
}); |
||||
|
||||
it('should be null', () => { |
||||
expect(resource.property).to.be.null; |
||||
}); |
||||
}); |
||||
|
||||
describe('when using $plain', () => { |
||||
var plain:any; |
||||
|
||||
beforeEach(() => { |
||||
source = { |
||||
_links: {self: {href: 'bunny'}}, |
||||
rabbit: 'fluffy' |
||||
}; |
||||
plain = new HalResource(source).$plain(); |
||||
}); |
||||
|
||||
it('should return an object that is equal to the source', () => { |
||||
expect(plain).to.eql(source); |
||||
}); |
||||
|
||||
it('should not be the exact same object', () => { |
||||
expect(plain).not.to.equal(source); |
||||
}); |
||||
}); |
||||
|
||||
describe('when creating a resource with a source that has no links', () => { |
||||
beforeEach(() => { |
||||
resource = new HalResource({}); |
||||
}); |
||||
|
||||
it('should return null for the href if it has no self link', () => { |
||||
expect(resource.href).to.equal(null); |
||||
}); |
||||
|
||||
it('should have a $link object with null href', () => { |
||||
expect(resource.$link.href).to.equal(null); |
||||
}); |
||||
}); |
||||
|
||||
describe('when creating a resource form a source with linked resources', () => { |
||||
beforeEach(() => { |
||||
source = { |
||||
_links: { |
||||
self: { |
||||
href: 'unicorn/69' |
||||
}, |
||||
beaver: { |
||||
href: 'justin/420' |
||||
} |
||||
} |
||||
}; |
||||
resource = new HalResource(source); |
||||
}); |
||||
|
||||
it('should have no "self" property', () => { |
||||
expect(resource.self).to.not.exist; |
||||
}); |
||||
|
||||
it('should have a beaver', () => { |
||||
expect(resource.beaver).to.exist; |
||||
}); |
||||
|
||||
it('should have no "_links" property', () => { |
||||
expect(resource._links).to.not.exist; |
||||
}); |
||||
|
||||
it('should leave the source accessible', () => { |
||||
expect(resource.$source).to.eql(source); |
||||
}); |
||||
|
||||
it('should have a callable self link', () => { |
||||
expect(() => resource.$links.self()).to.not.throw(Error); |
||||
}); |
||||
|
||||
it('should have a callable beaver', () => { |
||||
expect(() => resource.$links.beaver()).to.not.throw(Error); |
||||
}); |
||||
|
||||
it('should have a $links property with the keys of its source _links', () => { |
||||
const transformedLinks = Object.keys(resource.$links); |
||||
const plainLinks = Object.keys(source._links); |
||||
|
||||
expect(transformedLinks).to.have.members(plainLinks); |
||||
}); |
||||
}); |
||||
|
||||
describe('when creating a resource from a source with embedded resources', () => { |
||||
beforeEach(() => { |
||||
source = { |
||||
_embedded: { |
||||
resource: {_links: {}}, |
||||
} |
||||
}; |
||||
|
||||
resource = new HalResource(source); |
||||
}); |
||||
|
||||
it('should not have the original _embedded property', () => { |
||||
expect(resource._embedded).to.not.be.ok; |
||||
}); |
||||
|
||||
it('should have a property, that is a loaded resource', () => { |
||||
expect(resource.resource.$loaded).to.be.true; |
||||
}); |
||||
|
||||
it('should have an embedded resource, that is loaded', () => { |
||||
expect(resource.$embedded.resource.$loaded).to.be.true; |
||||
}); |
||||
|
||||
it('should have a property that is the resource', () => { |
||||
expect(resource.resource).to.equal(resource.$embedded.resource); |
||||
}); |
||||
|
||||
it.skip('should have a callable link to that resource', () => { |
||||
expect(() => resource.$links.resource()).to.not.throw(Error); |
||||
}); |
||||
|
||||
describe('when overriding the property with a resource', () => { |
||||
var link:HalLinkInterface; |
||||
|
||||
beforeEach(() => { |
||||
link = { |
||||
href: 'pony', |
||||
method: 'GET' |
||||
}; |
||||
resource.resource = HalResource.fromLink(link); |
||||
}); |
||||
|
||||
it('should set the property to that resource', () => { |
||||
expect(resource.resource.href).to.equal(link.href); |
||||
}); |
||||
|
||||
it.skip('should set the corresponding link', () => { |
||||
expect(resource.$links.resource.$link.href).to.equal(link.href); |
||||
}); |
||||
}); |
||||
|
||||
describe('when the embedded resources are nested', () => { |
||||
var first:any; |
||||
var deep:any; |
||||
|
||||
beforeEach(() => { |
||||
source._embedded.resource._embedded = { |
||||
first: { |
||||
_embedded: { |
||||
second: { |
||||
_links: {}, |
||||
property: 'yet another value' |
||||
} |
||||
}, |
||||
property: 'another value' |
||||
} |
||||
}; |
||||
|
||||
first = resource.$embedded.resource.$embedded.first; |
||||
deep = resource.$embedded.resource.$embedded.first.$embedded.second; |
||||
}); |
||||
|
||||
it('should crate all nested resources recursively', () => { |
||||
expect(deep.$isHal).to.be.true; |
||||
}); |
||||
|
||||
it('should transfer the properties of the nested resources correctly', () => { |
||||
expect(first.property).to.eq('another value'); |
||||
expect(deep.property).to.eq('yet another value'); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('when creating a resource from a source with a linked array property', () => { |
||||
var expectLengthsToBe = (length:any, update = 'update') => { |
||||
it(`should ${update} the values of the resource`, () => { |
||||
expect(resource.values).to.have.lengthOf(length); |
||||
}); |
||||
|
||||
it(`should ${update} the source`, () => { |
||||
expect(source._links.values).to.have.lengthOf(length); |
||||
}); |
||||
|
||||
it(`should ${update} the $source property`, () => { |
||||
expect(resource.$source._links.values).to.have.lengthOf(length); |
||||
}); |
||||
}; |
||||
|
||||
beforeEach(() => { |
||||
source = { |
||||
_links: { |
||||
values: [ |
||||
{ |
||||
href: '/api/value/1', |
||||
title: 'val1' |
||||
}, |
||||
{ |
||||
href: '/api/value/2', |
||||
title: 'val2' |
||||
} |
||||
] |
||||
} |
||||
}; |
||||
resource = new HalResource(source); |
||||
}); |
||||
|
||||
it('should be an array that is a property of the resource', () => { |
||||
expect(resource).to.have.property('values').that.is.an('array'); |
||||
}); |
||||
|
||||
expectLengthsToBe(2); |
||||
|
||||
describe('when adding resources to the array', () => { |
||||
beforeEach(() => { |
||||
resource.values.push(resource); |
||||
}); |
||||
expectLengthsToBe(3); |
||||
}); |
||||
|
||||
describe('when adding arbitrary values to the array', () => { |
||||
beforeEach(() => { |
||||
resource.values.push('something'); |
||||
}); |
||||
expectLengthsToBe(2, 'not update'); |
||||
}); |
||||
|
||||
describe('when removing resources from the array', () => { |
||||
beforeEach(() => { |
||||
resource.values.pop(); |
||||
}); |
||||
expectLengthsToBe(1); |
||||
}); |
||||
|
||||
describe('when each value is transformed', () => { |
||||
beforeEach(() => { |
||||
resource = resource.values[0]; |
||||
source = source._links.values[0]; |
||||
}); |
||||
|
||||
it('should have made each link a resource', () => { |
||||
expect(resource.$isHal).to.be.true; |
||||
}); |
||||
|
||||
it('should be resources generated from the links', () => { |
||||
expect(resource.href).to.eq(source.href); |
||||
}); |
||||
|
||||
it('should have a name attribute equal to the title of its link', () => { |
||||
expect(resource.name).to.eq(source.title); |
||||
}); |
||||
|
||||
it('should not be loaded', () => { |
||||
expect(resource.$loaded).to.be.false; |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('when transforming an object with an _embedded list with the list element having _links', () => { |
||||
beforeEach(() => { |
||||
source = { |
||||
_embedded: { |
||||
elements: [{_links: {}}, {_links: {}}] |
||||
} |
||||
}; |
||||
|
||||
resource = new HalResource(source); |
||||
}); |
||||
|
||||
it('should not have the original _embedded property', () => { |
||||
expect(resource._embedded).to.not.be.ok; |
||||
}); |
||||
|
||||
it('should transform the list elements', () => { |
||||
expect(resource.$embedded.elements[0].$isHal).to.be.true; |
||||
expect(resource.$embedded.elements[1].$isHal).to.be.true; |
||||
}); |
||||
}); |
||||
|
||||
describe('when transforming an object with _links and _embedded', () => { |
||||
beforeEach(() => { |
||||
source = { |
||||
_links: { |
||||
property: { |
||||
href: '/api/property', |
||||
title: 'Property' |
||||
}, |
||||
embedded: { |
||||
href: '/api/embedded', |
||||
}, |
||||
action: { |
||||
href: '/api/action', |
||||
method: 'post' |
||||
}, |
||||
self: { |
||||
href: '/api/self' |
||||
} |
||||
}, |
||||
_embedded: { |
||||
embedded: { |
||||
_links: { |
||||
self: { |
||||
href: '/api/embedded' |
||||
} |
||||
}, |
||||
name: 'name' |
||||
}, |
||||
notLinked: { |
||||
_links: { |
||||
self: { |
||||
href: '/api/not-linked' |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
|
||||
resource = new HalResource(source); |
||||
}); |
||||
|
||||
it('should be loaded', () => { |
||||
expect(resource.$loaded).to.be.true; |
||||
}); |
||||
|
||||
it('should not be possible to override a link', () => { |
||||
try { |
||||
resource.$links.action = 'foo'; |
||||
} catch (ignore) { |
||||
/**/ |
||||
} |
||||
|
||||
expect(resource.$links.action).to.not.eq('foo'); |
||||
}); |
||||
|
||||
it('should not be possible to override an embedded resource', () => { |
||||
try { |
||||
resource.$embedded.embedded = 'foo'; |
||||
} catch (ignore) { |
||||
/**/ |
||||
} |
||||
|
||||
expect(resource.$embedded.embedded).to.not.eq('foo'); |
||||
}); |
||||
|
||||
it('should have linked resources as properties', () => { |
||||
expect(resource.property).to.exist; |
||||
}); |
||||
|
||||
it('should have linked actions as properties', () => { |
||||
expect(resource.action).to.exist; |
||||
}); |
||||
|
||||
it('should have embedded resources as properties', () => { |
||||
expect(resource.embedded).to.exist; |
||||
}); |
||||
|
||||
it('should have embedded, but not linked resources as properties', () => { |
||||
expect(resource.notLinked).to.exist; |
||||
}); |
||||
|
||||
describe('when a resource that is linked and embedded is updated', () => { |
||||
var embeddedResource; |
||||
beforeEach(() => { |
||||
embeddedResource = { |
||||
$link: { |
||||
method: 'get', |
||||
href: 'newHref' |
||||
} |
||||
}; |
||||
|
||||
resource.embedded = embeddedResource; |
||||
}); |
||||
|
||||
it('should update the source', () => { |
||||
expect(resource.$source._links.embedded.href).to.eq('newHref'); |
||||
}); |
||||
}); |
||||
|
||||
describe('when after generating the properties from the links, each property', () => { |
||||
it('should be a function, if the link method is not "get"', () => { |
||||
expect(resource).to.respondTo('action'); |
||||
}); |
||||
|
||||
it('should be a resource, if the link method is "get"', () => { |
||||
expect(resource.property.$isHal).to.be.true; |
||||
}); |
||||
|
||||
describe('when a property is a resource', () => { |
||||
it('should not be callable', () => { |
||||
expect(resource).to.not.to.respondTo('property'); |
||||
}); |
||||
|
||||
it('should not be loaded initially', () => { |
||||
expect(resource.property.$loaded).to.be.false; |
||||
expect(resource.notLinked.$loaded).to.be.true; |
||||
}); |
||||
|
||||
it('should be loaded, if the resource is embedded', () => { |
||||
expect(resource.embedded.$loaded).to.be.true; |
||||
}); |
||||
|
||||
it('should update the source when set', () => { |
||||
resource.property = resource; |
||||
expect(resource.$source._links.property.href).to.eql('/api/self'); |
||||
}); |
||||
|
||||
describe('when loading it', () => { |
||||
beforeEach(() => { |
||||
resource = resource.property; |
||||
resource.$load(); |
||||
|
||||
$httpBackend.expectGET('/api/property').respond(200, { |
||||
_links: {}, |
||||
name: 'name', |
||||
foo: 'bar' |
||||
}); |
||||
$httpBackend.flush(); |
||||
}); |
||||
|
||||
it('should be loaded', () => { |
||||
expect(resource.$loaded).to.be.true; |
||||
}); |
||||
|
||||
it('should be updated', () => { |
||||
expect(resource.name).to.eq('name'); |
||||
}); |
||||
|
||||
it('should have properties that have a getter', () => { |
||||
expect((Object as any).getOwnPropertyDescriptor(resource, 'foo').get).to.exist; |
||||
}); |
||||
|
||||
it('should have properties that have a setter', () => { |
||||
expect((Object as any).getOwnPropertyDescriptor(resource, 'foo').set).to.exist; |
||||
}); |
||||
|
||||
it('should return itself in a promise if already loaded', () => { |
||||
resource.$loaded = 1; |
||||
|
||||
expect(resource.$load()).to.eventually.be.fulfilled.then(result => { |
||||
expect(result).to.equal(result); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
@ -1,394 +0,0 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
import {InputState} from "reactivestates"; |
||||
import {opApiModule} from "../../../../angular-modules"; |
||||
import {HalLink, HalLinkInterface} from "../hal-link/hal-link.service"; |
||||
import {HalResourceFactoryService} from "../hal-resource-factory/hal-resource-factory.service"; |
||||
|
||||
const ObservableArray:any = require('observable-array'); |
||||
|
||||
var $q:ng.IQService; |
||||
var lazy:Function; |
||||
var halLink:typeof HalLink; |
||||
var halResourceFactory:HalResourceFactoryService; |
||||
var CacheService:any; |
||||
|
||||
export class HalResource { |
||||
[attribute:string]:any; |
||||
public _type:string; |
||||
|
||||
public static create(element:any, force:boolean = false) { |
||||
if (_.isNil(element)) { |
||||
return element; |
||||
} |
||||
|
||||
|
||||
if (!force && !(element._embedded || element._links)) { |
||||
return element; |
||||
} |
||||
|
||||
return halResourceFactory.createHalResource(element); |
||||
} |
||||
|
||||
public static fromLink(link:HalLinkInterface) { |
||||
const resource = HalResource.getEmptyResource(halLink.fromObject(link)); |
||||
return new HalResource(resource, false); |
||||
} |
||||
|
||||
public static getEmptyResource(self:{href:string|null} = {href: null}):any { |
||||
return {_links: {self: self}}; |
||||
} |
||||
|
||||
public $links:any = {}; |
||||
public $embedded:any = {}; |
||||
public $self:ng.IPromise<this>; |
||||
|
||||
public _name:string; |
||||
|
||||
public static idFromLink(href:string):string { |
||||
return href.split('/').pop()!; |
||||
} |
||||
|
||||
public get idFromLink():string { |
||||
if (this.$href) { |
||||
return HalResource.idFromLink(this.$href); |
||||
} |
||||
|
||||
return ''; |
||||
} |
||||
|
||||
public get $isHal():boolean { |
||||
return true; |
||||
} |
||||
|
||||
public get $link():HalLinkInterface { |
||||
return this.$links.self.$link; |
||||
} |
||||
|
||||
public get name():string { |
||||
return this._name || this.$link.title || ''; |
||||
} |
||||
|
||||
public set name(name:string) { |
||||
this._name = name; |
||||
} |
||||
|
||||
/** |
||||
* Alias for $href. |
||||
* Please use $href instead. |
||||
* |
||||
* @deprecated |
||||
*/ |
||||
public get href():string|null { |
||||
return this.$link.href; |
||||
} |
||||
|
||||
public get $href():string|null { |
||||
return this.$link.href; |
||||
} |
||||
|
||||
constructor(public $source:any = HalResource.getEmptyResource(), |
||||
public $loaded:boolean = true) { |
||||
this.$initialize($source); |
||||
} |
||||
|
||||
/** |
||||
* Return the associated state to this HAL resource, if any. |
||||
*/ |
||||
public get state(): InputState<this> | null { |
||||
return null; |
||||
} |
||||
|
||||
public $load(force = false):ng.IPromise<this> { |
||||
if (!this.state) { |
||||
return this.$loadResource(force); |
||||
} |
||||
|
||||
const state = this.state; |
||||
|
||||
if (force) { |
||||
state.clear(); |
||||
} |
||||
|
||||
// If nobody has asked yet for the resource to be $loaded, do it ourselves.
|
||||
// Otherwise, we risk returning a promise, that will never be resolved.
|
||||
state.putFromPromiseIfPristine(() => this.$loadResource(force)); |
||||
|
||||
return <ng.IPromise<this>> state.valuesPromise().then(source => { |
||||
this.$initialize(source); |
||||
this.$loaded = true; |
||||
return this; |
||||
}); |
||||
} |
||||
|
||||
protected $loadResource(force = false):ng.IPromise<this> { |
||||
if (!force) { |
||||
if (this.$loaded) { |
||||
return $q.when(this); |
||||
} |
||||
|
||||
if (!this.$loaded && this.$self) { |
||||
return this.$self; |
||||
} |
||||
} |
||||
|
||||
// HACK: Remove cleared promise key from cache.
|
||||
// We should not be so clever as to do that, instead, rewrite this with states.
|
||||
if (force) { |
||||
CacheService.clearPromisedKey(this.$links.self.href); |
||||
} |
||||
// Reset and load this resource
|
||||
this.$loaded = false; |
||||
this.$self = this.$links.self({}, this.$loadHeaders(force)).then((source:any) => { |
||||
this.$loaded = true; |
||||
this.$initialize(source); |
||||
return this; |
||||
}); |
||||
|
||||
return this.$self; |
||||
} |
||||
|
||||
/** |
||||
* Update the resource ignoring the cache. |
||||
*/ |
||||
public $update() { |
||||
return this.$load(true); |
||||
} |
||||
|
||||
public $plain() { |
||||
return angular.copy(this.$source); |
||||
} |
||||
|
||||
public $copy() { |
||||
let clone:any = this.constructor |
||||
return new clone(_.cloneDeep(this.$source), this.$loaded);; |
||||
} |
||||
|
||||
public $initialize(source:any) { |
||||
this.$source = source.$source || source; |
||||
initializeResource(this); |
||||
} |
||||
|
||||
/** |
||||
* $load by default uses the $http cache. This will likely be replaced by |
||||
the HAL cache, but while it lasts, it should be ignored when using |
||||
force. |
||||
*/ |
||||
protected $loadHeaders(force:boolean) { |
||||
var headers:any = {}; |
||||
|
||||
if (force) { |
||||
headers.caching = {enabled: false}; |
||||
} |
||||
|
||||
return headers; |
||||
} |
||||
|
||||
/** |
||||
* Specify this resource's embedded keys that should be transformed with resources. |
||||
* Use this to restrict, e.g., links that should not be made properties if you have a custom get/setter. |
||||
*/ |
||||
public $embeddableKeys():string[] { |
||||
const properties = Object.keys(this.$source); |
||||
return _.without(properties, '_links', '_embedded'); |
||||
} |
||||
|
||||
/** |
||||
* Specify this resource's keys that should not be transformed with resources. |
||||
* Use this to restrict, e.g., links that should not be made properties if you have a custom get/setter. |
||||
*/ |
||||
public $linkableKeys():string[] { |
||||
const properties = Object.keys(this.$links); |
||||
return _.without(properties, 'self'); |
||||
} |
||||
|
||||
/** |
||||
* Get a linked resource from its HalLink with the correct ype |
||||
*/ |
||||
public createLinkedResource(linkName:string, link:HalLinkInterface) { |
||||
const resource = HalResource.getEmptyResource(); |
||||
const type = this.constructor._type; |
||||
resource._links.self = link; |
||||
|
||||
return halResourceFactory.createLinkedHalResource(resource, type, linkName); |
||||
} |
||||
} |
||||
|
||||
function initializeResource(halResource:HalResource) { |
||||
setSource(); |
||||
setupLinks(); |
||||
setupEmbedded(); |
||||
proxyProperties(); |
||||
setLinksAsProperties(); |
||||
setEmbeddedAsProperties(); |
||||
|
||||
function setSource() { |
||||
if (!halResource.$source._links) { |
||||
halResource.$source._links = {}; |
||||
} |
||||
|
||||
if (!halResource.$source._links.self) { |
||||
halResource.$source._links.self = new HalLink(); |
||||
} |
||||
} |
||||
|
||||
function proxyProperties() { |
||||
halResource.$embeddableKeys().forEach((property:any) => { |
||||
Object.defineProperty(halResource, property, { |
||||
get() { |
||||
return halResource.$source[property]; |
||||
}, |
||||
|
||||
set(value) { |
||||
halResource.$source[property] = value; |
||||
}, |
||||
|
||||
enumerable: true, |
||||
configurable: true |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
function setLinksAsProperties() { |
||||
halResource.$linkableKeys().forEach((linkName:string) => { |
||||
lazy(halResource, linkName, |
||||
() => { |
||||
const link:any = halResource.$links[linkName].$link || halResource.$links[linkName]; |
||||
|
||||
if (Array.isArray(link)) { |
||||
var items = link.map(item => halResource.createLinkedResource(linkName, item.$link)); |
||||
var property:HalResource[] = new ObservableArray(...items).on('change', () => { |
||||
property.forEach(item => { |
||||
if (!item.$link) { |
||||
property.splice(property.indexOf(item), 1); |
||||
} |
||||
}); |
||||
|
||||
halResource.$source._links[linkName] = property.map(item => item.$link); |
||||
}); |
||||
|
||||
return property; |
||||
} |
||||
|
||||
if (link.href) { |
||||
if (link.method !== 'get') { |
||||
return HalLink.callable(link); |
||||
} |
||||
|
||||
return halResource.createLinkedResource(linkName, link); |
||||
} |
||||
|
||||
return null; |
||||
}, |
||||
(val:any) => setter(val, linkName) |
||||
); |
||||
}); |
||||
} |
||||
|
||||
function setEmbeddedAsProperties() { |
||||
if (!halResource.$source._embedded) { |
||||
return; |
||||
} |
||||
|
||||
Object.keys(halResource.$source._embedded).forEach(name => { |
||||
lazy(halResource, name, () => halResource.$embedded[name], (val:any) => setter(val, name)); |
||||
}); |
||||
} |
||||
|
||||
function setupProperty(name:string, callback:(element:any) => any) { |
||||
const instanceName = '$' + name; |
||||
const sourceName = '_' + name; |
||||
const sourceObj = halResource.$source[sourceName]; |
||||
|
||||
if (angular.isObject(sourceObj)) { |
||||
Object.keys(sourceObj).forEach(propName => { |
||||
lazy((halResource as any)[instanceName], propName, () => callback(sourceObj[propName])); |
||||
}); |
||||
} |
||||
} |
||||
|
||||
function setupLinks() { |
||||
setupProperty('links', |
||||
link => Array.isArray(link) ? link.map(HalLink.callable) : HalLink.callable(link)); |
||||
} |
||||
|
||||
function setupEmbedded() { |
||||
setupProperty('embedded', element => { |
||||
angular.forEach(element, (child:any, name:string) => { |
||||
if (child && (child._embedded || child._links)) { |
||||
lazy(element, name, () => HalResource.create(child)); |
||||
} |
||||
}); |
||||
|
||||
if (Array.isArray(element)) { |
||||
return element.map((source) => HalResource.create(source, true)); |
||||
} |
||||
|
||||
return HalResource.create(element); |
||||
}); |
||||
} |
||||
|
||||
function setter(val:HalResource|{ href?: string }, linkName:string) { |
||||
if (!val) { |
||||
halResource.$source._links[linkName] = {href: null}; |
||||
} else if (_.isArray(val)) { |
||||
halResource.$source._links[linkName] = val.map((el:any) => { return {href: el.href} }); |
||||
} else if (val.hasOwnProperty('$link')) { |
||||
const link = (val as HalResource).$link; |
||||
|
||||
if (link.href) { |
||||
halResource.$source._links[linkName] = link; |
||||
} |
||||
} else if ('href' in val) { |
||||
halResource.$source._links[linkName] = {href: val.href}; |
||||
} |
||||
|
||||
if (halResource.$embedded && halResource.$embedded[linkName]) { |
||||
halResource.$embedded[linkName] = val; |
||||
halResource.$source._embedded[linkName] = _.get(val, '$source', val); |
||||
} |
||||
|
||||
return val; |
||||
} |
||||
} |
||||
|
||||
function halResourceService(...args:any[]) { |
||||
[$q, lazy, halLink, halResourceFactory, CacheService] = args; |
||||
return HalResource; |
||||
} |
||||
|
||||
halResourceService.$inject = [ |
||||
'$q', |
||||
'lazy', |
||||
'HalLink', |
||||
'halResourceFactory', |
||||
'CacheService' |
||||
]; |
||||
|
||||
opApiModule.factory('HalResource', halResourceService); |
@ -1,41 +0,0 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
import {SchemaResource, SchemaAttributeObject} from './schema-resource.service'; |
||||
import {CollectionResource} from './collection-resource.service'; |
||||
import {QueryColumn} from '../../../wp-query/query-column'; |
||||
import {QuerySortByResource} from './query-sort-by-resource.service'; |
||||
import {QueryGroupByResource} from './query-group-by-resource.service'; |
||||
import {QueryFilterInstanceSchemaResource} from "core-components/api/api-v3/hal-resources/query-filter-instance-schema-resource.service"; |
||||
|
||||
export interface QuerySchemaResourceInterface extends SchemaResource { |
||||
columns:{ allowedValues: QueryColumn[] }; |
||||
filtersSchemas: CollectionResource<QueryFilterInstanceSchemaResource>; |
||||
sortBy:{ allowedValues: QuerySortByResource[] }; |
||||
groupBy:{ allowedValues: QueryGroupByResource[] }; |
||||
} |
@ -1,455 +0,0 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
import {opApiModule} from '../../../../angular-modules'; |
||||
import {WorkPackageCacheService} from '../../../work-packages/work-package-cache.service'; |
||||
import IHttpBackendService = angular.IHttpBackendService; |
||||
import IQService = angular.IQService; |
||||
import IRootScopeService = angular.IRootScopeService; |
||||
import IPromise = angular.IPromise; |
||||
|
||||
describe('WorkPackageResource service', () => { |
||||
var $httpBackend: IHttpBackendService; |
||||
var $rootScope: IRootScopeService; |
||||
var $q: IQService; |
||||
var WorkPackageResource:any; |
||||
var AttachmentCollectionResource:any; |
||||
var wpCacheService: WorkPackageCacheService; |
||||
var NotificationsService: any; |
||||
var wpNotificationsService: any; |
||||
|
||||
var source: any; |
||||
var workPackage: any; |
||||
|
||||
const createWorkPackage = () => { |
||||
source = source || { id: 'new' }; |
||||
workPackage = new WorkPackageResource(source); |
||||
}; |
||||
|
||||
const expectUncachedRequest = (href:string) => { |
||||
$httpBackend |
||||
.expectGET(href, (headers:any) => headers.caching.enabled === false) |
||||
.respond(200, {_links: {self: {href}}}); |
||||
}; |
||||
|
||||
const expectUncachedRequests = (...urls:string[]) => { |
||||
urls.forEach(expectUncachedRequest); |
||||
$httpBackend.flush(); |
||||
}; |
||||
|
||||
beforeEach(angular.mock.module(opApiModule.name)); |
||||
beforeEach(angular.mock.inject(function (_$httpBackend_:any, |
||||
_$rootScope_:any, |
||||
_$q_:any, |
||||
_WorkPackageResource_:any, |
||||
_AttachmentCollectionResource_:any, |
||||
_wpCacheService_:any, |
||||
_NotificationsService_:any, |
||||
_wpNotificationsService_:any) { |
||||
[ |
||||
$httpBackend, |
||||
$rootScope, |
||||
$q, |
||||
WorkPackageResource, |
||||
AttachmentCollectionResource, |
||||
wpCacheService, |
||||
NotificationsService, |
||||
wpNotificationsService |
||||
] = _.toArray(arguments); |
||||
})); |
||||
|
||||
it('should exist', () => { |
||||
expect(WorkPackageResource).to.exist; |
||||
}); |
||||
|
||||
describe('when creating an empty work package', () => { |
||||
beforeEach(createWorkPackage); |
||||
|
||||
it('should have an attachments property of type `AttachmentCollectionResource`', () => { |
||||
expect(workPackage.attachments).to.be.instanceOf(AttachmentCollectionResource); |
||||
}); |
||||
|
||||
it('should return true for `isNew`', () => { |
||||
expect(workPackage.isNew).to.be.true; |
||||
}); |
||||
}); |
||||
|
||||
describe('when retrieving `canAddAttachment`', () => { |
||||
beforeEach(createWorkPackage); |
||||
|
||||
const expectValue = (value:any, prepare = () => angular.noop()) => { |
||||
value = value.toString(); |
||||
|
||||
beforeEach(prepare); |
||||
it('should be ' + value, () => { |
||||
(expect(workPackage.canAddAttachments).to.be as any)[value]; |
||||
}); |
||||
}; |
||||
|
||||
describe('when the work package is new', () => { |
||||
expectValue(true); |
||||
}); |
||||
|
||||
describe('when the work package is not new', () => { |
||||
expectValue(false, () => { |
||||
workPackage.id = 420; |
||||
}); |
||||
}); |
||||
|
||||
describe('when the work work package has no `addAttachment` link and is not new', () => { |
||||
expectValue(false, () => { |
||||
workPackage.id = 69; |
||||
workPackage.$links.addAttachment = null; |
||||
}); |
||||
}); |
||||
|
||||
describe('when the work work package has an `addAttachment` link', () => { |
||||
expectValue(true, () => { |
||||
workPackage.$links.addAttachment = <any> angular.noop; |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('when updating multiple linked resources', () => { |
||||
var updateWorkPackageStub: sinon.SinonStub; |
||||
var result:Promise<any>; |
||||
|
||||
const expectCacheUpdate = () => { |
||||
it('should update the work package cache', () => { |
||||
result.then(() => { |
||||
expect(updateWorkPackageStub.calledWith(workPackage.id)).to.be.true; |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
beforeEach(() => { |
||||
updateWorkPackageStub = sinon.stub(wpCacheService, 'touch'); |
||||
}); |
||||
|
||||
afterEach(() => { |
||||
$rootScope.$apply(); |
||||
updateWorkPackageStub.restore(); |
||||
}); |
||||
|
||||
describe('when the resources are properties of the work package', () => { |
||||
const testResultIsResource = (href:string, prepare:any) => { |
||||
beforeEach(prepare); |
||||
expectCacheUpdate(); |
||||
|
||||
it('should be a promise with a resource where the $href is ' + href, () => { |
||||
expect(result).to.eventually.have.property('$href', href); |
||||
}); |
||||
}; |
||||
|
||||
beforeEach(() => { |
||||
source = { |
||||
_links: { |
||||
schema: { _type: 'Schema', href: 'schema' }, |
||||
attachments: { href: 'attachmentsHref' }, |
||||
activities: { href: 'activitiesHref' } |
||||
} |
||||
}; |
||||
createWorkPackage(); |
||||
}); |
||||
|
||||
describe('when updating the properties using updateLinkedResources()', () => { |
||||
var results:any; |
||||
|
||||
beforeEach(() => { |
||||
results = workPackage.updateLinkedResources('attachments', 'activities'); |
||||
expectUncachedRequests('attachmentsHref', 'activitiesHref'); |
||||
}); |
||||
|
||||
it('should return a result, that has the same properties as the updated ones', () => { |
||||
expect(results).to.eventually.be.fulfilled.then(results => { |
||||
expect(Object.keys(results)).to.have.members(['attachments', 'activities']); |
||||
}); |
||||
}); |
||||
|
||||
testResultIsResource('attachmentsHref', () => { |
||||
results.then((results:any) => result = $q.when(results.attachments)); |
||||
}); |
||||
|
||||
testResultIsResource('activitiesHref', () => { |
||||
results.then((results:any) => result = $q.when(results.activities)); |
||||
}); |
||||
}); |
||||
|
||||
describe('when updating the activities', () => { |
||||
testResultIsResource('activitiesHref', () => { |
||||
result = workPackage.updateActivities(); |
||||
expectUncachedRequests('activitiesHref'); |
||||
}); |
||||
}); |
||||
|
||||
describe('when updating the attachments', () => { |
||||
testResultIsResource('attachmentsHref', () => { |
||||
result = workPackage.updateAttachments(); |
||||
expectUncachedRequests('activitiesHref', 'attachmentsHref'); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('when the linked resource are not properties of the work package', () => { |
||||
const expectRejectedWithCacheUpdate = (prepare:any) => { |
||||
beforeEach(prepare); |
||||
|
||||
it('should return a rejected promise', () => { |
||||
expect(result).to.eventually.be.rejected; |
||||
}); |
||||
|
||||
expectCacheUpdate(); |
||||
}; |
||||
|
||||
beforeEach(() => { |
||||
source = {}; |
||||
createWorkPackage(); |
||||
}); |
||||
|
||||
describe('when using updateLinkedResources', () => { |
||||
expectRejectedWithCacheUpdate(() => { |
||||
result = workPackage.updateLinkedResources('attachments', 'activities'); |
||||
}); |
||||
}); |
||||
|
||||
describe('when using updateActivities', () => { |
||||
expectRejectedWithCacheUpdate(() => { |
||||
result = workPackage.updateActivities(); |
||||
}); |
||||
}); |
||||
|
||||
describe('when using updateAttachments', () => { |
||||
expectRejectedWithCacheUpdate(() => { |
||||
result = workPackage.updateAttachments(); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('when a work package is created with attachments and activities', () => { |
||||
beforeEach(() => { |
||||
source = { |
||||
_links: { |
||||
schema: { _type: 'Schema', href: 'schema' }, |
||||
attachments: { href: 'attachments' }, |
||||
activities: { href: 'activities' } |
||||
}, |
||||
isNew: true |
||||
}; |
||||
createWorkPackage(); |
||||
}); |
||||
|
||||
describe('when adding multiple attachments to the work package', () => { |
||||
var file: any = {}; |
||||
var files: any[] = [file, file]; |
||||
var uploadFilesDeferred:any; |
||||
var uploadAttachmentsPromise:any; |
||||
var attachmentsUploadStub:any; |
||||
var uploadNotificationStub:any; |
||||
|
||||
beforeEach(() => { |
||||
uploadFilesDeferred = $q.defer(); |
||||
const uploadResult = { |
||||
uploads: [uploadFilesDeferred.promise], |
||||
finished: uploadFilesDeferred.promise |
||||
}; |
||||
attachmentsUploadStub = sinon.stub(workPackage.attachments, 'upload').returns(uploadResult); |
||||
uploadNotificationStub = sinon.stub(NotificationsService, 'addWorkPackageUpload'); |
||||
|
||||
uploadAttachmentsPromise = workPackage.uploadAttachments(files); |
||||
}); |
||||
|
||||
it('should call the upload method of the attachment collection resource', () => { |
||||
expect(attachmentsUploadStub.calledWith(files)).to.be.true; |
||||
}); |
||||
|
||||
it('should add an upload notification', () => { |
||||
expect(uploadNotificationStub.calledOnce).to.be.true; |
||||
}); |
||||
|
||||
describe('when the upload fails', () => { |
||||
var notificationStub:any; |
||||
var error = 'err'; |
||||
|
||||
beforeEach(() => { |
||||
uploadFilesDeferred.reject(error); |
||||
notificationStub = sinon.stub(wpNotificationsService, 'handleRawError'); |
||||
$rootScope.$apply(); |
||||
}); |
||||
|
||||
it('should call the error response notification', () => { |
||||
expect(notificationStub.calledWith(error, workPackage)).to.be.true; |
||||
}); |
||||
}); |
||||
|
||||
describe('when the upload succeeds', () => { |
||||
var removeStub:any; |
||||
var updateWorkPackageStub:any; |
||||
var updateWorkPackageTouchStub: sinon.SinonStub; |
||||
|
||||
beforeEach(() => { |
||||
updateWorkPackageStub = sinon.stub(wpCacheService, 'updateWorkPackage'); |
||||
uploadFilesDeferred.resolve(); |
||||
updateWorkPackageTouchStub = sinon.stub(wpCacheService, 'touch'); |
||||
removeStub = sinon.stub(NotificationsService, 'remove'); |
||||
|
||||
expectUncachedRequest('activities'); |
||||
expectUncachedRequest('attachments'); |
||||
$httpBackend |
||||
.when('GET', 'schema') |
||||
.respond(200, {_links: {self: 'schema'}}); |
||||
$httpBackend.flush(); |
||||
$rootScope.$apply(); |
||||
}); |
||||
|
||||
it('should remove the upload notification', angular.mock.inject(($timeout:ng.ITimeoutService) => { |
||||
$timeout.flush(); |
||||
expect(removeStub.calledOnce).to.be.true; |
||||
})); |
||||
|
||||
it('should return an attachment collection resource promise', () => { |
||||
expect(uploadAttachmentsPromise).to.eventually.have.property('$href', 'attachments'); |
||||
$rootScope.$apply(); |
||||
}); |
||||
|
||||
afterEach(() => { |
||||
updateWorkPackageStub.restore(); |
||||
updateWorkPackageTouchStub.restore(); |
||||
removeStub.restore(); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('when using uploadPendingAttachments', () => { |
||||
var uploadAttachmentsStub: sinon.SinonStub; |
||||
|
||||
beforeEach(() => { |
||||
workPackage.pendingAttachments.push({}, {}); |
||||
uploadAttachmentsStub = sinon |
||||
.stub(workPackage, 'uploadAttachments') |
||||
.returns($q.when()); |
||||
}); |
||||
|
||||
beforeEach(() => { |
||||
workPackage.isNew = false; |
||||
workPackage.uploadPendingAttachments(); |
||||
}); |
||||
|
||||
it('should call the uploadAttachments method with the pendingAttachments', () => { |
||||
expect(uploadAttachmentsStub.calledWith([{},{}])).to.be.true; |
||||
}); |
||||
|
||||
describe('when the upload succeeds', () => { |
||||
beforeEach(() => { |
||||
$rootScope.$apply(); |
||||
}); |
||||
|
||||
it('should reset the pending attachments', () => { |
||||
expect(workPackage.pendingAttachments).to.have.length(0); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('when using removeAttachment', () => { |
||||
var file:any; |
||||
var attachment:any; |
||||
|
||||
beforeEach(() => { |
||||
file = {}; |
||||
attachment = { |
||||
$isHal: true, |
||||
'delete': sinon.stub() |
||||
}; |
||||
|
||||
createWorkPackage(); |
||||
workPackage.attachments = {elements: [attachment]}; |
||||
workPackage.pendingAttachments.push(file); |
||||
}); |
||||
|
||||
describe('when the attachment is a regular file', () => { |
||||
beforeEach(() => { |
||||
workPackage.removeAttachment(file); |
||||
}); |
||||
|
||||
it('should be removed from the pending attachments', () => { |
||||
expect(workPackage.pendingAttachments).to.have.length(0); |
||||
}); |
||||
}); |
||||
|
||||
describe('when the attachment is an attachment resource', () => { |
||||
var deletion:any; |
||||
|
||||
beforeEach(() => { |
||||
deletion = $q.defer(); |
||||
attachment.delete.returns(deletion.promise); |
||||
sinon.stub(workPackage, 'updateAttachments'); |
||||
|
||||
workPackage.removeAttachment(attachment); |
||||
}); |
||||
|
||||
it('should call its delete method', () => { |
||||
expect(attachment.delete.calledOnce).to.be.true; |
||||
}); |
||||
|
||||
describe('when the deletion gets resolved', () => { |
||||
beforeEach(() => { |
||||
deletion.resolve(); |
||||
$rootScope.$apply(); |
||||
}); |
||||
|
||||
it('should call updateAttachments()', () => { |
||||
expect(workPackage.updateAttachments.calledOnce).to.be.true; |
||||
}); |
||||
}); |
||||
|
||||
describe('when an error occurs', () => { |
||||
var error:any; |
||||
|
||||
beforeEach(() => { |
||||
error = {foo: 'bar'}; |
||||
sinon.stub(wpNotificationsService, 'handleErrorResponse'); |
||||
deletion.reject(error); |
||||
$rootScope.$apply(); |
||||
}); |
||||
|
||||
it('should call the handleErrorResponse notification', () => { |
||||
const calledWithErrorAndWorkPackage = wpNotificationsService |
||||
.handleErrorResponse |
||||
.calledWith(error, workPackage); |
||||
|
||||
expect(calledWithErrorAndWorkPackage).to.be.true; |
||||
}); |
||||
|
||||
it('should not remove the attachment from the elements array', () => { |
||||
expect(workPackage.attachments.elements).to.have.length(1); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
@ -1,141 +0,0 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
function CacheService($q:ng.IQService, CacheFactory:any) { |
||||
|
||||
// Temporary storage for currently resolving promises
|
||||
var _promises:{[key:string]: ng.IPromise<any>} = {}; |
||||
|
||||
// Global switch to disable all caches
|
||||
var disabled = false; |
||||
|
||||
var CacheService = { |
||||
|
||||
temporaryCache: function() { |
||||
return CacheService.customCache('openproject-session_cache', { |
||||
maxAge: 10 * 60 * 1000, // 10 mins
|
||||
storageMode: 'sessionStorage' |
||||
}); |
||||
}, |
||||
|
||||
localStorage: function() { |
||||
return CacheService.customCache('openproject-local_storage_cache', { |
||||
storageMode: 'localStorage' |
||||
}); |
||||
}, |
||||
|
||||
memoryStorage: function() { |
||||
return CacheService.customCache('openproject-memory_storage_cache', { |
||||
storageMode: 'memory' |
||||
}); |
||||
}, |
||||
|
||||
customCache: function(identifier:string, params:any) { |
||||
var _cache = CacheFactory.get(identifier); |
||||
|
||||
if (!_cache) { |
||||
_cache = CacheFactory(identifier, params); |
||||
} |
||||
|
||||
if (disabled) { |
||||
_cache.disable(); |
||||
} |
||||
|
||||
return _cache; |
||||
}, |
||||
|
||||
isCacheDisabled: function() { |
||||
return disabled; |
||||
}, |
||||
|
||||
enableCaching: function() { |
||||
disabled = false; |
||||
}, |
||||
|
||||
disableCaching: function() { |
||||
disabled = true; |
||||
}, |
||||
|
||||
clearPromisedKey: function(key:string, options:any) { |
||||
options = options || {}; |
||||
var cache = options.cache || CacheService.memoryStorage(); |
||||
cache.remove(key); |
||||
}, |
||||
|
||||
cachedPromise: function(promiseFn:() => ng.IPromise<any>, key:string, options:any) { |
||||
options = options || {}; |
||||
var cache = options.cache || CacheService.memoryStorage(); |
||||
var force = options.force || false; |
||||
var deferred = $q.defer(); |
||||
var cachedValue, promise; |
||||
|
||||
// Return early when frontend caching is not desired
|
||||
if (cache.disabled) { |
||||
return promiseFn(); |
||||
} |
||||
|
||||
// Got the result directly? Great.
|
||||
cachedValue = cache.get(key); |
||||
if (cachedValue && !force) { |
||||
deferred.resolve(cachedValue); |
||||
return deferred.promise; |
||||
} |
||||
|
||||
// Return an existing promise if it exists
|
||||
// Avoids intermittent requests while a first
|
||||
// is already underway.
|
||||
if (_promises[key]) { |
||||
return _promises[key]; |
||||
} |
||||
|
||||
// Call now to retrieve promise
|
||||
promise = promiseFn(); |
||||
promise |
||||
.then(data => { |
||||
cache.put(key, data); |
||||
deferred.resolve(data); |
||||
}) |
||||
.catch(error => { |
||||
deferred.reject(error); |
||||
cache.remove(key); |
||||
}) |
||||
.finally(() => delete _promises[key]); |
||||
|
||||
_promises[key] = deferred.promise; |
||||
return deferred.promise; |
||||
}, |
||||
}; |
||||
|
||||
return CacheService; |
||||
} |
||||
|
||||
|
||||
angular |
||||
.module('openproject.services') |
||||
.factory('CacheService', CacheService); |
||||
|
@ -1,131 +0,0 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
|
||||
import {UserResource} from '../../api/api-v3/hal-resources/user-resource.service'; |
||||
describe('revisionActivity Directive', function () { |
||||
var compile:any, element:any, rootScope:any, scope:any, I18n:any, $q:any; |
||||
|
||||
beforeEach(angular.mock.module('openproject.workPackages.activities')); |
||||
beforeEach(function () { |
||||
angular.mock.module( |
||||
'openproject.api', |
||||
'openproject.workPackages', |
||||
'openproject.models', |
||||
'openproject.services', |
||||
'openproject.config', |
||||
'openproject.templates' |
||||
); |
||||
}); |
||||
|
||||
beforeEach(inject(function ($rootScope:any, $compile:any, _I18n_:any, _$q_:any) { |
||||
var html:string; |
||||
html = '<revision-activity work-package="workPackage" activity="activity" activity-no="activityNo" is-initial="isInitial"></revision-activity>'; |
||||
|
||||
rootScope = $rootScope; |
||||
$q = _$q_; |
||||
scope = $rootScope.$new(); |
||||
|
||||
I18n = _I18n_; |
||||
sinon.stub(I18n, 't').returns(''); |
||||
|
||||
|
||||
compile = function () { |
||||
element = angular.element(html); |
||||
$compile(element)(scope); |
||||
scope.$digest(); |
||||
}; |
||||
})); |
||||
|
||||
afterEach(function () { |
||||
I18n.t.restore(); |
||||
}); |
||||
|
||||
describe('with a valid revision', function () { |
||||
beforeEach(function () { |
||||
scope.workPackage = { |
||||
revisions: true |
||||
}; |
||||
scope.activity = { |
||||
showRevision: { |
||||
$link: { href: '/project/foo/repository/revision/1234' }, |
||||
}, |
||||
|
||||
id: 1, |
||||
identifier: '11f4b07dff4f4ce9548a52b7d002daca7cd63ec6', |
||||
formattedIdentifier: '11f4b07', |
||||
authorName: 'some developer', |
||||
message: { |
||||
format: 'plain', |
||||
raw: 'This revision provides new features\n\nAn elaborate description', |
||||
html: '<p>This revision provides new features<br><br>An elaborate description</p>' |
||||
}, |
||||
createdAt: '2015-07-21T13:36:59Z' |
||||
}; |
||||
compile(); |
||||
}); |
||||
|
||||
it('should not render an image', function () { |
||||
expect(element.find('.avatar')).to.have.length(0); |
||||
}); |
||||
|
||||
it('should have the author name, but no link', function () { |
||||
expect(element.find('.user').html()).to.equal('some developer'); |
||||
expect(element.find('.user > a')).to.have.length(0); |
||||
}); |
||||
|
||||
describe('with linked author', function () { |
||||
beforeEach(function () { |
||||
scope.activity.author = { |
||||
$load: function () { |
||||
return $q.when(new UserResource({ |
||||
id: 1, |
||||
name: 'Some Dude', |
||||
avatar: 'avatar.png', |
||||
status: 'active' |
||||
}, true)); |
||||
} |
||||
}; |
||||
compile(); |
||||
}); |
||||
|
||||
it('should render a user profile', function () { |
||||
expect(element.find('.avatar').attr('alt')).to.equal('Avatar'); |
||||
expect(element.find('span.user > a').text()).to.equal('Some Dude'); |
||||
}); |
||||
}); |
||||
|
||||
describe('message', function () { |
||||
it('should render commit message', function () { |
||||
var message = element.find('.user-comment > span.message').html(); |
||||
|
||||
expect(message).to.eq(scope.activity.message.html); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue