Commit e0b8b81ddf982ba6a7f4fc661b2afd95171950b3

Authored by Ábner Oliveira
2 parents 6e1dcd9e af9fc87e

Merge branch 'refactor-articles-watchers' into 'master'

Refactor articles watchers

See merge request !27
src/app/article/article-default-view-component.spec.ts
... ... @@ -68,13 +68,13 @@ describe("Components", () => {
68 68 */
69 69 function doDeleteArticle() {
70 70 // Create a mock for the ArticleService removeArticle method
71   - spyOn(helper.component.articleService, 'removeArticle').and.callFake(function(param: noosfero.Article) {
  71 + spyOn(helper.component.articleService, 'remove').and.callFake(function(param: noosfero.Article) {
72 72 return {
73 73 catch: () => {}
74 74 };
75 75 });
76 76 helper.component.delete();
77   - expect(articleService.removeArticle).toHaveBeenCalled();
  77 + expect(articleService.remove).toHaveBeenCalled();
78 78 // After the component delete method execution, fire the
79 79 // ArticleEvent.removed event
80 80 simulateRemovedEvent();
... ... @@ -84,7 +84,7 @@ describe("Components", () => {
84 84 * Simulate the ArticleService ArticleEvent.removed event
85 85 */
86 86 function simulateRemovedEvent() {
87   - helper.component.articleService["notifyArticleRemovedListeners"](article);
  87 + helper.component.articleService["modelRemovedEventEmitter"].next(article);
88 88 }
89 89 });
90 90  
... ...
src/app/article/article-default-view.component.ts
... ... @@ -5,7 +5,7 @@ import {MacroDirective} from "./macro/macro.directive";
5 5 import {ArticleToolbarHotspotComponent} from "../hotspot/article-toolbar-hotspot.component";
6 6 import {ArticleContentHotspotComponent} from "../hotspot/article-content-hotspot.component";
7 7 import {ArticleService} from "./../../lib/ng-noosfero-api/http/article.service";
8   -
  8 +import {NotificationService} from "./../shared/services/notification.service";
9 9 /**
10 10 * @ngdoc controller
11 11 * @name ArticleDefaultView
... ... @@ -25,7 +25,7 @@ export class ArticleDefaultViewComponent {
25 25  
26 26 constructor(private $state: ng.ui.IStateService, public articleService: ArticleService) {
27 27 // Subscribe to the Article Removed Event
28   - this.articleService.subscribeToArticleRemoved((article: noosfero.Article) => {
  28 + this.articleService.subscribeToModelRemoved((article: noosfero.Article) => {
29 29 if (this.article.parent) {
30 30 this.$state.transitionTo('main.profile.page', { page: this.article.parent.path, profile: this.article.profile.identifier });
31 31 } else {
... ... @@ -35,9 +35,7 @@ export class ArticleDefaultViewComponent {
35 35 }
36 36  
37 37 delete() {
38   - this.articleService.removeArticle(this.article).catch((cause: any) => {
39   - throw new Error(`Problem removing the article: ${cause}`);
40   - });
  38 + this.articleService.remove(this.article);
41 39 }
42 40  
43 41 }
... ...
src/app/article/types/blog/blog.component.spec.ts
1   -import {
2   -providers
3   -} from 'ng-forward/cjs/testing/providers';
4   -
5   -import {
6   -Input,
7   -Component
8   -} from 'ng-forward';
9   -import {
10   -ArticleBlogComponent
11   -} from './blog.component';
12   -
13   -import {
14   -createComponentFromClass,
15   -quickCreateComponent,
16   -provideEmptyObjects,
17   -createProviderToValue,
18   -provideFilters
19   -} from "../../../../spec/helpers.ts";
  1 +import {providers} from 'ng-forward/cjs/testing/providers';
  2 +
  3 +import {Input, provide, Component} from 'ng-forward';
  4 +import {ArticleBlogComponent} from './blog.component';
  5 +
  6 +import {createComponentFromClass, quickCreateComponent, provideEmptyObjects, createProviderToValue, provideFilters} from "../../../../spec/helpers.ts";
  7 +
  8 +import {ComponentTestHelper, createClass} from './../../../../spec/component-test-helper';
20 9  
21 10 // this htmlTemplate will be re-used between the container components in this spec file
22 11 const htmlTemplate: string = '<noosfero-blog [article]="ctrl.article" [profile]="ctrl.profile"></noosfero-blog>';
... ... @@ -42,38 +31,30 @@ describe(&quot;Blog Component&quot;, () =&gt; {
42 31 }
43 32 }
44 33  
  34 + let article1 = <noosfero.Article>{
  35 + id: 1,
  36 + title: 'The article test'
  37 + };
  38 +
  39 + let article2 = <noosfero.Article>{
  40 + id: 1,
  41 + title: 'The article test'
  42 + };
  43 +
  44 + let articles = [ article1, article2 ];
  45 +
45 46 let articleService = {
46 47 getChildren: (article_id: number, filters: {}) => {
47 48 return promiseResultTemplate(null);
48   - }
  49 + },
  50 + subscribeToArticleRemoved: (fn: Function) => {}
49 51 };
50 52  
51   - @Component({
52   - selector: 'test-container-component',
53   - template: htmlTemplate,
54   - directives: [ArticleBlogComponent],
55   - providers: [
56   - provideEmptyObjects('Restangular'),
57   - createProviderToValue('ArticleService', articleService),
58   - provideFilters('truncateFilter')
59   - ]
60   - })
61   - class BlogContainerComponent {
62   - article = {
63   - type: 'anyArticleType'
64   - };
65   - profile = {
66   - name: 'profile-name'
67   - };
68   - }
  53 + let helper: ComponentTestHelper<ArticleBlogComponent>;
69 54  
70   - beforeEach(() => {
  55 + beforeEach(angular.mock.module("templates"));
71 56  
72   - // the karma preprocessor html2js transform the templates html into js files which put
73   - // the templates to the templateCache into the module templates
74   - // we need to load the module templates here as the template for the
75   - // component Noosfero ArtileView will be load on our tests
76   - angular.mock.module("templates");
  57 + beforeEach((done) => {
77 58  
78 59 providers((provide: any) => {
79 60 return <any>[
... ... @@ -82,48 +63,26 @@ describe(&quot;Blog Component&quot;, () =&gt; {
82 63 })
83 64 ];
84 65 });
85   - });
86   -
87   - it("renders the blog content", (done: Function) => {
88   -
89   - createComponentFromClass(BlogContainerComponent).then((fixture) => {
90   -
91   - expect(fixture.debugElement.query('div.blog').length).toEqual(1);
92   -
93   - done();
  66 + let providersHelper = [
  67 + provide('ArticleService', { useValue: articleService })
  68 + ];
  69 + let cls = createClass({
  70 + template: htmlTemplate,
  71 + directives: [ArticleBlogComponent],
  72 + providers: providersHelper,
  73 + properties: {
  74 + posts: articles
  75 + }
94 76 });
  77 + helper = new ComponentTestHelper<ArticleBlogComponent>(cls, done);
95 78 });
96 79  
97   - it("verify the blog data", (done: Function) => {
98   -
99   - let articles = [{
100   - id: 1,
101   - title: 'The article test'
102   - }];
103   -
104   - let result = { data: articles, headers: (name: string) => { return 1; } };
105   -
106   - // defining a mock result to articleService.getChildren method
107   - articleService.getChildren = (article_id: number, filters: {}) => {
108   - return promiseResultTemplate(result);
109   - };
110   -
111   - createComponentFromClass(BlogContainerComponent).then((fixture) => {
112   -
113   - // gets the children component of BlogContainerComponent
114   - let articleBlog: BlogContainerComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
115   -
116   - // check if the component property are the provided by the mocked articleService
117   - let post = {
118   - id: 1,
119   - title: 'The article test'
120   - };
121   - expect((<any>articleBlog)["posts"][0]).toEqual(jasmine.objectContaining(post));
122   - expect((<any>articleBlog)["totalPosts"]).toEqual(1);
123   -
124   - done();
125   - });
  80 + it("renders the blog content", () => {
  81 + expect(helper.debugElement.query('div.blog').length).toEqual(1);
  82 + });
126 83  
  84 + it("verify the blog data", () => {
  85 + expect(helper.component["posts"][0]).toEqual(jasmine.objectContaining(article1));
127 86 });
128 87  
129 88 });
130 89 \ No newline at end of file
... ...
src/app/layout/blocks/recent-documents/recent-documents-block.component.spec.ts
... ... @@ -2,6 +2,7 @@ import {TestComponentBuilder} from &#39;ng-forward/cjs/testing/test-component-builde
2 2 import {Provider, Input, provide, Component} from 'ng-forward';
3 3 import {provideFilters} from '../../../../spec/helpers';
4 4 import {RecentDocumentsBlockComponent} from './recent-documents-block.component';
  5 +import * as helpers from "./../../../../spec/helpers";
5 6  
6 7 const htmlTemplate: string = '<noosfero-recent-documents-block [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-recent-documents-block>';
7 8  
... ... @@ -11,11 +12,13 @@ describe(&quot;Components&quot;, () =&gt; {
11 12 describe("Recent Documents Block Component", () => {
12 13  
13 14 let settingsObj = {};
  15 + let article = <noosfero.Article>{ name: "article1" };
14 16 let mockedBlockService = {
15 17 getApiContent: (block: noosfero.Block): any => {
16   - return Promise.resolve({ articles: [{ name: "article1" }], headers: (name: string) => { return name; } });
  18 + return Promise.resolve({ articles: [article], headers: (name: string) => { return name; } });
17 19 }
18 20 };
  21 + let articleService: any = helpers.mocks.articleService;
19 22 let profile = { name: 'profile-name' };
20 23 beforeEach(angular.mock.module("templates"));
21 24  
... ... @@ -28,6 +31,7 @@ describe(&quot;Components&quot;, () =&gt; {
28 31 new Provider('BlockService', {
29 32 useValue: mockedBlockService
30 33 }),
  34 + new Provider('ArticleService', { useValue: articleService })
31 35 ].concat(provideFilters("truncateFilter", "stripTagsFilter"));
32 36 }
33 37 let componentClass: any = null;
... ... @@ -47,7 +51,7 @@ describe(&quot;Components&quot;, () =&gt; {
47 51 it("get recent documents from the block service", done => {
48 52 tcb.createAsync(getComponent()).then(fixture => {
49 53 let recentDocumentsBlock: RecentDocumentsBlockComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
50   - expect(recentDocumentsBlock.documents).toEqual([{ name: "article1" }]);
  54 + expect(recentDocumentsBlock.documents).toEqual([article]);
51 55 done();
52 56 });
53 57 });
... ... @@ -61,5 +65,22 @@ describe(&quot;Components&quot;, () =&gt; {
61 65 });
62 66 });
63 67  
  68 + it("verify removed article has been removed from list", done => {
  69 + tcb.createAsync(getComponent()).then(fixture => {
  70 + let recentDocumentsBlock: RecentDocumentsBlockComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
  71 + expect(recentDocumentsBlock.documents.length).toEqual(1);
  72 + simulateRemovedEvent(recentDocumentsBlock);
  73 + expect(recentDocumentsBlock.documents.length).toEqual(0);
  74 + done();
  75 + });
  76 + });
  77 +
  78 + /**
  79 + * Simulate the ArticleService ArticleEvent.removed event
  80 + */
  81 + function simulateRemovedEvent(recentDocumentsBlock: RecentDocumentsBlockComponent) {
  82 + recentDocumentsBlock.articleService["modelRemovedEventEmitter"].next(article);
  83 + }
  84 +
64 85 });
65 86 });
... ...
src/app/layout/blocks/recent-documents/recent-documents-block.component.ts
1 1 import {Component, Inject, Input} from "ng-forward";
2 2 import {BlockService} from "../../../../lib/ng-noosfero-api/http/block.service";
  3 +import {ArticleService} from "./../../../../lib/ng-noosfero-api/http/article.service";
  4 +import {Arrays} from "./../../../../lib/util/arrays";
3 5  
4 6 @Component({
5 7 selector: "noosfero-recent-documents-block",
6 8 templateUrl: 'app/layout/blocks/recent-documents/recent-documents-block.html'
7 9 })
8   -@Inject(BlockService, "$state")
  10 +@Inject(BlockService, "$state", ArticleService)
9 11 export class RecentDocumentsBlockComponent {
10 12  
11 13 @Input() block: any;
... ... @@ -15,7 +17,7 @@ export class RecentDocumentsBlockComponent {
15 17 documents: any;
16 18 documentsLoaded: boolean = false;
17 19  
18   - constructor(private blockService: BlockService, private $state: any) { }
  20 + constructor(private blockService: BlockService, private $state: any, public articleService: ArticleService) { }
19 21  
20 22 ngOnInit() {
21 23 this.profile = this.owner;
... ... @@ -24,6 +26,13 @@ export class RecentDocumentsBlockComponent {
24 26 this.documents = content.articles;
25 27 this.documentsLoaded = true;
26 28 });
  29 + this.watchArticles();
  30 + }
  31 +
  32 + watchArticles() {
  33 + this.articleService.subscribeToModelRemoved((article: noosfero.Article) => {
  34 + Arrays.remove(this.documents, article);
  35 + });
27 36 }
28 37  
29 38 openDocument(article: any) {
... ...
src/app/layout/blocks/statistics/statistics-block.component.spec.ts
  1 +import {provide} from 'ng-forward';
1 2 import {ComponentTestHelper, createClass} from './../../../../spec/component-test-helper';
2 3 import {StatisticsBlockComponent} from './statistics-block.component';
3 4 import * as helpers from "../../../../spec/helpers";
4 5  
5 6 const htmlTemplate: string = '<noosfero-statistics-block [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-statistics-block>';
6 7  
  8 +
7 9 describe("Components", () => {
8 10  
9 11 describe("Statistics Block Component", () => {
... ... @@ -11,10 +13,15 @@ describe(&quot;Components&quot;, () =&gt; {
11 13 beforeEach(angular.mock.module("templates"));
12 14  
13 15 beforeEach((done) => {
  16 + let articleService: any = helpers.mocks.articleService;
  17 + let blockService: any = jasmine.createSpyObj("blockService", ["getBlock"]);
14 18 let cls = createClass({
15 19 template: htmlTemplate,
16 20 directives: [StatisticsBlockComponent],
17   - providers: helpers.provideFilters("translateFilter"),
  21 + providers: [
  22 + provide('ArticleService', { useValue: articleService }),
  23 + provide('BlockService', { useValue: blockService })
  24 + ].concat(helpers.provideFilters("translateFilter")),
18 25 properties: {
19 26 block: {
20 27 statistics: [
... ...
src/app/layout/blocks/statistics/statistics-block.component.ts
1   -import {Input, Inject, Component} from "ng-forward";
2   -
  1 +import {Input, Inject, Component, provide} from "ng-forward";
  2 +import {ArticleService} from "./../../../../lib/ng-noosfero-api/http/article.service";
  3 +import {BlockService} from "./../../../../lib/ng-noosfero-api/http/block.service";
3 4 @Component({
4 5 selector: "noosfero-statistics-block",
5 6 templateUrl: 'app/layout/blocks/statistics/statistics-block.html'
6 7 })
7 8  
  9 +@Inject(ArticleService, BlockService)
8 10 export class StatisticsBlockComponent {
9 11 @Input() block: noosfero.StatisticsBlock;
10 12 @Input() owner: any;
  13 +
  14 + constructor(articleService: ArticleService, blockService: BlockService) {
  15 + // watches for article being removed
  16 + // to update comments and tag statistics, which would
  17 + // changed after removing an article
  18 + articleService.subscribeToModelRemoved(() => {
  19 + blockService.getBlock<noosfero.StatisticsBlock>(this.block.id)
  20 + .then(blockFromAPI => this.block = blockFromAPI);
  21 + });
  22 +
  23 + articleService.subscribeToModelAdded(() => {
  24 + blockService.getBlock<noosfero.StatisticsBlock>(this.block.id)
  25 + .then(blockFromAPI => this.block = blockFromAPI);
  26 + });
  27 + }
11 28 }
... ...
src/app/shared/utils/arrays.ts
... ... @@ -1,19 +0,0 @@
1   -export class ArrayUtils {
2   -
3   - /**
4   - * Returns if two arrays are equal
5   - */
6   - static arraysEqual(a: any, b: any): boolean {
7   - if (a === b) return true;
8   - if (a == null || b == null) return false;
9   - if (a.length !== b.length) return false;
10   -
11   - // If you don't care about the order of the elements inside
12   - // the array, you should sort both arrays here.
13   - for (let i = 0; i < a.length; ++i) {
14   - if (a[i] !== b[i]) return false;
15   - }
16   - return true;
17   - }
18   -
19   -}
20 0 \ No newline at end of file
src/app/shared/utils/hashmap.ts
... ... @@ -1,36 +0,0 @@
1   -export class HashMap<K extends EqualsObject, V> {
2   - private values: Array<HashItem<K, V>> = new Array();
3   -
4   - get(key: K): V {
5   - let found = this.values.find( function(value: HashItem<K, V>) {
6   - return value.key.equals(key);
7   - });
8   - if (found) {
9   - return found.value;
10   - }
11   - return null;
12   - }
13   -
14   - put(key: K, value: V): void {
15   - this.values.push(new HashItem(key, value));
16   - }
17   -
18   - clear() {
19   - this.values = new Array();
20   - }
21   -}
22   -
23   -export class HashItem<K, V> {
24   - key: K;
25   - value: V;
26   - constructor(key: K, value: V) {
27   - this.key = key;
28   - this.value = value;
29   - }
30   -}
31   -
32   -export abstract class EqualsObject {
33   -
34   - abstract equals(other: any): boolean;
35   -
36   -}
37 0 \ No newline at end of file
src/app/shared/utils/index.ts
... ... @@ -1,3 +0,0 @@
1   -/* Module Index Entry - generated using the script npm run generate-index */
2   -export * from "./hashmap";
3   -export * from "./arrays";
src/lib/ng-noosfero-api/http/article.service.spec.ts
... ... @@ -23,7 +23,7 @@ describe(&quot;Services&quot;, () =&gt; {
23 23 it("should remove article", (done) => {
24 24 let articleId = 1;
25 25 $httpBackend.expectDELETE(`/api/v1/articles/${articleId}`).respond(200, { success: "true" });
26   - articleService.removeArticle(<noosfero.Article>{id: articleId});
  26 + articleService.remove(<noosfero.Article>{id: articleId});
27 27 $httpBackend.flush();
28 28 $httpBackend.verifyNoOutstandingExpectation();
29 29 done();
... ...
src/lib/ng-noosfero-api/http/article.service.ts
... ... @@ -7,8 +7,6 @@ import {NoosferoRootScope} from &quot;./../../../app/shared/models/interfaces&quot;;
7 7 @Inject("Restangular", "$q", "$log", ProfileService)
8 8 export class ArticleService extends RestangularService<noosfero.Article> {
9 9  
10   - private articleRemoved: EventEmitter<noosfero.Article> = new EventEmitter<noosfero.Article>();
11   -
12 10 constructor(Restangular: restangular.IService, $q: ng.IQService, $log: ng.ILogService, protected profileService: ProfileService) {
13 11 super(Restangular, $q, $log);
14 12 }
... ... @@ -24,30 +22,22 @@ export class ArticleService extends RestangularService&lt;noosfero.Article&gt; {
24 22 };
25 23 }
26 24  
27   - removeArticle(article: noosfero.Article) {
28   - let restRequest: ng.IPromise<noosfero.RestResult<noosfero.Article>> = this.remove(article);
29   - let deferred = this.$q.defer<noosfero.RestResult<noosfero.Article>>();
30   - restRequest.then((result: any) => {
31   - this.notifyArticleRemovedListeners(article);
32   - }).catch(this.getHandleErrorFunction(deferred));
33   - return deferred.promise;
34   - }
  25 + // removeArticle(article: noosfero.Article) {
  26 + // // let restRequest: ng.IPromise<noosfero.RestResult<noosfero.Article>> = this.remove(article);
  27 + // // let deferred = this.$q.defer<noosfero.RestResult<noosfero.Article>>();
  28 + // // restRequest.then((result: any) => {
  29 + // // this.notifyArticleRemovedListeners(article);
  30 + // // }).catch(this.getHandleErrorFunction(deferred));
  31 + // // return deferred.promise;
  32 + // }
35 33  
36 34 /**
37 35 * Notify listeners that this article has been removed
38 36 */
39   - private notifyArticleRemovedListeners(article: noosfero.Article) {
40   - // let listener = this.events.get(this.removed);
41   - // listener.next(article);
42   - this.articleRemoved.next(article);
43   - }
  37 + // private notifyArticleRemovedListeners(article: noosfero.Article) {
  38 + // this.modelRemovedEventEmitter.next(article);
  39 + // }
44 40  
45   - /**
46   - * subscribes to the ArticleRemoved event emitter
47   - */
48   - subscribeToArticleRemoved(fn: Function) {
49   - this.articleRemoved.subscribe(fn);
50   - }
51 41  
52 42 updateArticle(article: noosfero.Article) {
53 43 let headers = {
... ...
src/lib/ng-noosfero-api/http/block.service.ts
... ... @@ -37,4 +37,14 @@ export class BlockService extends RestangularService&lt;noosfero.Block&gt; {
37 37 return apiContentPromise.promise;
38 38 }
39 39  
  40 + getBlock<T extends noosfero.Block>(blockId: number): ng.IPromise<T> {
  41 + let deferred = this.$q.defer<T>();
  42 + this.get(blockId)
  43 + .then((result: noosfero.RestResult<T>) => {
  44 + deferred.resolve(result.data);
  45 + })
  46 + .catch(reason => deferred.reject(reason));
  47 + return deferred.promise;
  48 + }
  49 +
40 50 }
... ...
src/lib/ng-noosfero-api/http/restangular_service.ts
  1 +import {EventEmitter} from "ng-forward";
1 2 /**
2 3 * @name RestangularService
3 4 * Base class to be extended by classes which will provide access
... ... @@ -13,6 +14,11 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; {
13 14 private baseResource: restangular.IElement;
14 15 private currentPromise: ng.IDeferred<T>;
15 16  
  17 + protected modelFoundEventEmitter: EventEmitter<T> = new EventEmitter<any>();
  18 + protected modelAddedEventEmitter: EventEmitter<T> = new EventEmitter<any>();
  19 + protected modelRemovedEventEmitter: EventEmitter<T> = new EventEmitter<any>();
  20 + protected modelUpdatedEventEmitter: EventEmitter<T> = new EventEmitter<any>();
  21 +
16 22 /**
17 23 * Creates an instance of RestangularService.
18 24 *
... ... @@ -35,6 +41,22 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; {
35 41 // });
36 42 }
37 43  
  44 + subscribeToModelRemoved(fn: ((model: T) => void)) {
  45 + this.modelRemovedEventEmitter.subscribe(fn);
  46 + }
  47 +
  48 + subscribeToModelAdded(fn: ((model: T) => void)) {
  49 + this.modelAddedEventEmitter.subscribe(fn);
  50 + }
  51 +
  52 + subscribeToModelUpdated(fn: ((model: T) => void)) {
  53 + this.modelUpdatedEventEmitter.subscribe(fn);
  54 + }
  55 +
  56 + subscribeToModelFound(fn: ((model: T) => void)) {
  57 + this.modelFoundEventEmitter.subscribe(fn);
  58 + }
  59 +
38 60 public resetCurrent() {
39 61 this.currentPromise = this.$q.defer();
40 62 }
... ... @@ -107,7 +129,7 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; {
107 129 restRequest = this.restangularService.one(this.getResourcePath(), id).get(queryParams, headers);
108 130 }
109 131  
110   - restRequest.then(this.getHandleSuccessFunction(deferred))
  132 + restRequest.then(this.getHandleSuccessFunction(deferred, this.modelFoundEventEmitter))
111 133 .catch(this.getHandleErrorFunction(deferred));
112 134  
113 135  
... ... @@ -201,7 +223,7 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; {
201 223 restRequest = restangularObj.remove(queryParams, headers);
202 224  
203 225 restRequest
204   - .then(this.getHandleSuccessFunction(deferred))
  226 + .then(this.getHandleSuccessFunction(deferred, this.modelRemovedEventEmitter))
205 227 .catch(this.getHandleErrorFunction(deferred));
206 228  
207 229 return deferred.promise;
... ... @@ -226,7 +248,7 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; {
226 248  
227 249 restRequest = restangularObj.put(queryParams, headers);
228 250  
229   - restRequest.then(this.getHandleSuccessFunction(deferred))
  251 + restRequest.then(this.getHandleSuccessFunction(deferred, this.modelUpdatedEventEmitter))
230 252 .catch(this.getHandleErrorFunction(deferred));
231 253  
232 254 return deferred.promise;
... ... @@ -255,7 +277,7 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; {
255 277 restRequest = this.baseResource.post(data, queryParams, headers);
256 278 }
257 279  
258   - restRequest.then(this.getHandleSuccessFunction(deferred))
  280 + restRequest.then(this.getHandleSuccessFunction(deferred, this.modelAddedEventEmitter))
259 281 .catch(this.getHandleErrorFunction(deferred));
260 282  
261 283 return deferred.promise;
... ... @@ -289,7 +311,7 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; {
289 311 }
290 312  
291 313 /** HANDLERS */
292   - protected getHandleSuccessFunction<C>(deferred: ng.IDeferred<noosfero.RestResult<C | T | any>>, responseKey?: string): (response: restangular.IResponse) => void {
  314 + protected getHandleSuccessFunction<C>(deferred: ng.IDeferred<noosfero.RestResult<C | T | any>>, successEmitter: EventEmitter<T> = null): (response: restangular.IResponse) => void {
293 315 let self = this;
294 316  
295 317 /**
... ... @@ -301,7 +323,13 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; {
301 323 if (self.$log) {
302 324 self.$log.debug("Request successfull executed", response.data, self, response);
303 325 }
304   - deferred.resolve(<any>this.extractData(response));
  326 + let resultModel: noosfero.RestResult<T> = <any>this.extractData(response);
  327 + // resolve the promise with the model returned from the Noosfero API
  328 + deferred.resolve(resultModel);
  329 + // emits the event if a successEmiter was provided in the successEmitter parameter
  330 + if (successEmitter !== null) {
  331 + successEmitter.next(resultModel);
  332 + }
305 333 };
306 334 return successFunction;
307 335 }
... ...
src/lib/util/arrays.ts 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +export class Arrays {
  2 + static remove<T extends noosfero.RestModel>(elements: T[], element: T) {
  3 + elements.forEach((value: T, index: number, array: T[]) => {
  4 + if (value.id === element.id) {
  5 + array.splice(index, 1);
  6 + }
  7 + });
  8 + }
  9 +}
0 10 \ No newline at end of file
... ...
src/plugins/comment_paragraph/block/discussion/discussion-block.component.spec.ts
... ... @@ -3,6 +3,7 @@ import {Provider, Input, provide, Component} from &#39;ng-forward&#39;;
3 3 import {provideFilters} from '../../../../spec/helpers';
4 4 import {DiscussionBlockComponent} from './discussion-block.component';
5 5 import {ComponentTestHelper, createClass} from './../../../../spec/component-test-helper';
  6 +import * as helpers from "./../../../../spec/helpers";
6 7  
7 8 const htmlTemplate: string = '<noosfero-comment-paragraph-plugin-discussion-block [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-comment-paragraph-plugin-discussion-block>';
8 9  
... ... @@ -13,11 +14,13 @@ describe(&quot;Components&quot;, () =&gt; {
13 14  
14 15 let helper: ComponentTestHelper<DiscussionBlockComponent>;
15 16 let settingsObj = {};
  17 + let article = <noosfero.Article>{ name: "article1" };
16 18 let mockedBlockService = {
17 19 getApiContent: (content: any): any => {
18   - return Promise.resolve({ articles: [{ name: "article1" }], headers: (name: string) => { return name; } });
  20 + return Promise.resolve({ articles: [article], headers: (name: string) => { return name; } });
19 21 }
20 22 };
  23 + let articleService: any = helpers.mocks.articleService;
21 24 let profile = { name: 'profile-name' };
22 25  
23 26 let state = jasmine.createSpyObj("state", ["go"]);
... ... @@ -27,6 +30,7 @@ describe(&quot;Components&quot;, () =&gt; {
27 30 new Provider('BlockService', {
28 31 useValue: mockedBlockService
29 32 }),
  33 + new Provider('ArticleService', { useValue: articleService })
30 34 ].concat(provideFilters("truncateFilter", "stripTagsFilter", "translateFilter", "amDateFormatFilter"));
31 35  
32 36 beforeEach(angular.mock.module("templates"));
... ... @@ -51,5 +55,17 @@ describe(&quot;Components&quot;, () =&gt; {
51 55 block.openDocument({ path: "path", profile: { identifier: "identifier" } });
52 56 expect(state.go).toHaveBeenCalledWith("main.profile.page", { page: "path", profile: "identifier" });
53 57 });
  58 +
  59 + it("verify removed article has been removed from list", () => {
  60 + expect(helper.component.documents.length).toEqual(1);
  61 + simulateRemovedEvent();
  62 + expect(helper.component.documents.length).toEqual(0);
  63 + });
  64 + /**
  65 + * Simulate the ArticleService ArticleEvent.removed event
  66 + */
  67 + function simulateRemovedEvent() {
  68 + helper.component.articleService["modelRemovedEventEmitter"].next(article);
  69 + }
54 70 });
55 71 });
... ...
src/plugins/comment_paragraph/block/discussion/discussion-block.component.ts
1 1 import {Component, Inject, Input} from "ng-forward";
2 2 import {BlockService} from "../../../../lib/ng-noosfero-api/http/block.service";
  3 +import {ArticleService} from "./../../../../lib/ng-noosfero-api/http/article.service";
  4 +import {Arrays} from "./../../../../lib/util/arrays";
3 5  
4 6 @Component({
5 7 selector: "noosfero-comment-paragraph-plugin-discussion-block",
6 8 templateUrl: 'plugins/comment_paragraph/block/discussion/discussion-block.html'
7 9 })
8   -@Inject(BlockService, "$state")
  10 +@Inject(BlockService, "$state", ArticleService)
9 11 export class DiscussionBlockComponent {
10 12  
11 13 @Input() block: any;
... ... @@ -14,7 +16,7 @@ export class DiscussionBlockComponent {
14 16 profile: noosfero.Profile;
15 17 documents: Array<noosfero.Article>;
16 18  
17   - constructor(private blockService: BlockService, private $state: any) { }
  19 + constructor(private blockService: BlockService, private $state: any, public articleService: ArticleService) { }
18 20  
19 21 ngOnInit() {
20 22 this.profile = this.owner;
... ... @@ -22,6 +24,13 @@ export class DiscussionBlockComponent {
22 24 this.documents = content.articles;
23 25 this.block.hide = !this.documents || this.documents.length === 0;
24 26 });
  27 + this.watchArticles();
  28 + }
  29 +
  30 + watchArticles() {
  31 + this.articleService.subscribeToModelRemoved((article: noosfero.Article) => {
  32 + Arrays.remove(this.documents, article);
  33 + });
25 34 }
26 35  
27 36 openDocument(article: any) {
... ...
src/spec/mocks.ts
... ... @@ -77,10 +77,14 @@ export var mocks: any = {
77 77 },
78 78 articleService: {
79 79 articleRemovedFn: null,
80   - subscribeToArticleRemoved: (fn: Function) => {
  80 + articleAddedFn: null,
  81 + subscribeToModelRemoved: (fn: Function) => {
81 82 mocks.articleService.articleRemovedFn = fn;
82 83 },
83   - articleRemoved:
  84 + subscribeToModelAdded: (fn: Function) => {
  85 + mocks.articleService.articleAddedFn = fn;
  86 + },
  87 + modelRemovedEventEmitter:
84 88 {
85 89 subscribe: (fn: Function) => {
86 90 mocks.articleService.articleRemovedFn = fn;
... ... @@ -90,18 +94,22 @@ export var mocks: any = {
90 94 }
91 95 }
92 96 ,
93   - removeArticle: (article: noosfero.Article) => {
  97 + modelAddedEventEmitter:
  98 + {
  99 + subscribe: (fn: Function) => {
  100 + mocks.articleService.articleAddedFn = fn;
  101 + },
  102 + next: (param: any) => {
  103 + mocks.articleService.articleAddedFn(param);
  104 + }
  105 + }
  106 + ,
  107 + remove: (article: noosfero.Article) => {
94 108 return {
95 109 catch: (func?: Function) => {
96 110 }
97 111 };
98 112 },
99   - notifyArticleRemovedListeners: (article: noosfero.Article) => {
100   - mocks.articleService.articleRemoved.next(article);
101   - },
102   - subscribe: (eventType: any, fn: Function) => {
103   - mocks.articleService.articleRemoved.subscribe(fn);
104   - },
105 113 getByProfile: (profileId: number, params?: any) => {
106 114 return {
107 115 then: (func?: Function) => {
... ...