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,13 +68,13 @@ describe("Components", () => {
68 */ 68 */
69 function doDeleteArticle() { 69 function doDeleteArticle() {
70 // Create a mock for the ArticleService removeArticle method 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 return { 72 return {
73 catch: () => {} 73 catch: () => {}
74 }; 74 };
75 }); 75 });
76 helper.component.delete(); 76 helper.component.delete();
77 - expect(articleService.removeArticle).toHaveBeenCalled(); 77 + expect(articleService.remove).toHaveBeenCalled();
78 // After the component delete method execution, fire the 78 // After the component delete method execution, fire the
79 // ArticleEvent.removed event 79 // ArticleEvent.removed event
80 simulateRemovedEvent(); 80 simulateRemovedEvent();
@@ -84,7 +84,7 @@ describe("Components", () => { @@ -84,7 +84,7 @@ describe("Components", () => {
84 * Simulate the ArticleService ArticleEvent.removed event 84 * Simulate the ArticleService ArticleEvent.removed event
85 */ 85 */
86 function simulateRemovedEvent() { 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,7 +5,7 @@ import {MacroDirective} from "./macro/macro.directive";
5 import {ArticleToolbarHotspotComponent} from "../hotspot/article-toolbar-hotspot.component"; 5 import {ArticleToolbarHotspotComponent} from "../hotspot/article-toolbar-hotspot.component";
6 import {ArticleContentHotspotComponent} from "../hotspot/article-content-hotspot.component"; 6 import {ArticleContentHotspotComponent} from "../hotspot/article-content-hotspot.component";
7 import {ArticleService} from "./../../lib/ng-noosfero-api/http/article.service"; 7 import {ArticleService} from "./../../lib/ng-noosfero-api/http/article.service";
8 - 8 +import {NotificationService} from "./../shared/services/notification.service";
9 /** 9 /**
10 * @ngdoc controller 10 * @ngdoc controller
11 * @name ArticleDefaultView 11 * @name ArticleDefaultView
@@ -25,7 +25,7 @@ export class ArticleDefaultViewComponent { @@ -25,7 +25,7 @@ export class ArticleDefaultViewComponent {
25 25
26 constructor(private $state: ng.ui.IStateService, public articleService: ArticleService) { 26 constructor(private $state: ng.ui.IStateService, public articleService: ArticleService) {
27 // Subscribe to the Article Removed Event 27 // Subscribe to the Article Removed Event
28 - this.articleService.subscribeToArticleRemoved((article: noosfero.Article) => { 28 + this.articleService.subscribeToModelRemoved((article: noosfero.Article) => {
29 if (this.article.parent) { 29 if (this.article.parent) {
30 this.$state.transitionTo('main.profile.page', { page: this.article.parent.path, profile: this.article.profile.identifier }); 30 this.$state.transitionTo('main.profile.page', { page: this.article.parent.path, profile: this.article.profile.identifier });
31 } else { 31 } else {
@@ -35,9 +35,7 @@ export class ArticleDefaultViewComponent { @@ -35,9 +35,7 @@ export class ArticleDefaultViewComponent {
35 } 35 }
36 36
37 delete() { 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 // this htmlTemplate will be re-used between the container components in this spec file 10 // this htmlTemplate will be re-used between the container components in this spec file
22 const htmlTemplate: string = '<noosfero-blog [article]="ctrl.article" [profile]="ctrl.profile"></noosfero-blog>'; 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,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 let articleService = { 46 let articleService = {
46 getChildren: (article_id: number, filters: {}) => { 47 getChildren: (article_id: number, filters: {}) => {
47 return promiseResultTemplate(null); 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 providers((provide: any) => { 59 providers((provide: any) => {
79 return <any>[ 60 return <any>[
@@ -82,48 +63,26 @@ describe(&quot;Blog Component&quot;, () =&gt; { @@ -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 \ No newline at end of file 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,6 +2,7 @@ import {TestComponentBuilder} from &#39;ng-forward/cjs/testing/test-component-builde
2 import {Provider, Input, provide, Component} from 'ng-forward'; 2 import {Provider, Input, provide, Component} from 'ng-forward';
3 import {provideFilters} from '../../../../spec/helpers'; 3 import {provideFilters} from '../../../../spec/helpers';
4 import {RecentDocumentsBlockComponent} from './recent-documents-block.component'; 4 import {RecentDocumentsBlockComponent} from './recent-documents-block.component';
  5 +import * as helpers from "./../../../../spec/helpers";
5 6
6 const htmlTemplate: string = '<noosfero-recent-documents-block [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-recent-documents-block>'; 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,11 +12,13 @@ describe(&quot;Components&quot;, () =&gt; {
11 describe("Recent Documents Block Component", () => { 12 describe("Recent Documents Block Component", () => {
12 13
13 let settingsObj = {}; 14 let settingsObj = {};
  15 + let article = <noosfero.Article>{ name: "article1" };
14 let mockedBlockService = { 16 let mockedBlockService = {
15 getApiContent: (block: noosfero.Block): any => { 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 let profile = { name: 'profile-name' }; 22 let profile = { name: 'profile-name' };
20 beforeEach(angular.mock.module("templates")); 23 beforeEach(angular.mock.module("templates"));
21 24
@@ -28,6 +31,7 @@ describe(&quot;Components&quot;, () =&gt; { @@ -28,6 +31,7 @@ describe(&quot;Components&quot;, () =&gt; {
28 new Provider('BlockService', { 31 new Provider('BlockService', {
29 useValue: mockedBlockService 32 useValue: mockedBlockService
30 }), 33 }),
  34 + new Provider('ArticleService', { useValue: articleService })
31 ].concat(provideFilters("truncateFilter", "stripTagsFilter")); 35 ].concat(provideFilters("truncateFilter", "stripTagsFilter"));
32 } 36 }
33 let componentClass: any = null; 37 let componentClass: any = null;
@@ -47,7 +51,7 @@ describe(&quot;Components&quot;, () =&gt; { @@ -47,7 +51,7 @@ describe(&quot;Components&quot;, () =&gt; {
47 it("get recent documents from the block service", done => { 51 it("get recent documents from the block service", done => {
48 tcb.createAsync(getComponent()).then(fixture => { 52 tcb.createAsync(getComponent()).then(fixture => {
49 let recentDocumentsBlock: RecentDocumentsBlockComponent = fixture.debugElement.componentViewChildren[0].componentInstance; 53 let recentDocumentsBlock: RecentDocumentsBlockComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
50 - expect(recentDocumentsBlock.documents).toEqual([{ name: "article1" }]); 54 + expect(recentDocumentsBlock.documents).toEqual([article]);
51 done(); 55 done();
52 }); 56 });
53 }); 57 });
@@ -61,5 +65,22 @@ describe(&quot;Components&quot;, () =&gt; { @@ -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 import {Component, Inject, Input} from "ng-forward"; 1 import {Component, Inject, Input} from "ng-forward";
2 import {BlockService} from "../../../../lib/ng-noosfero-api/http/block.service"; 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 @Component({ 6 @Component({
5 selector: "noosfero-recent-documents-block", 7 selector: "noosfero-recent-documents-block",
6 templateUrl: 'app/layout/blocks/recent-documents/recent-documents-block.html' 8 templateUrl: 'app/layout/blocks/recent-documents/recent-documents-block.html'
7 }) 9 })
8 -@Inject(BlockService, "$state") 10 +@Inject(BlockService, "$state", ArticleService)
9 export class RecentDocumentsBlockComponent { 11 export class RecentDocumentsBlockComponent {
10 12
11 @Input() block: any; 13 @Input() block: any;
@@ -15,7 +17,7 @@ export class RecentDocumentsBlockComponent { @@ -15,7 +17,7 @@ export class RecentDocumentsBlockComponent {
15 documents: any; 17 documents: any;
16 documentsLoaded: boolean = false; 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 ngOnInit() { 22 ngOnInit() {
21 this.profile = this.owner; 23 this.profile = this.owner;
@@ -24,6 +26,13 @@ export class RecentDocumentsBlockComponent { @@ -24,6 +26,13 @@ export class RecentDocumentsBlockComponent {
24 this.documents = content.articles; 26 this.documents = content.articles;
25 this.documentsLoaded = true; 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 openDocument(article: any) { 38 openDocument(article: any) {
src/app/layout/blocks/statistics/statistics-block.component.spec.ts
  1 +import {provide} from 'ng-forward';
1 import {ComponentTestHelper, createClass} from './../../../../spec/component-test-helper'; 2 import {ComponentTestHelper, createClass} from './../../../../spec/component-test-helper';
2 import {StatisticsBlockComponent} from './statistics-block.component'; 3 import {StatisticsBlockComponent} from './statistics-block.component';
3 import * as helpers from "../../../../spec/helpers"; 4 import * as helpers from "../../../../spec/helpers";
4 5
5 const htmlTemplate: string = '<noosfero-statistics-block [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-statistics-block>'; 6 const htmlTemplate: string = '<noosfero-statistics-block [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-statistics-block>';
6 7
  8 +
7 describe("Components", () => { 9 describe("Components", () => {
8 10
9 describe("Statistics Block Component", () => { 11 describe("Statistics Block Component", () => {
@@ -11,10 +13,15 @@ describe(&quot;Components&quot;, () =&gt; { @@ -11,10 +13,15 @@ describe(&quot;Components&quot;, () =&gt; {
11 beforeEach(angular.mock.module("templates")); 13 beforeEach(angular.mock.module("templates"));
12 14
13 beforeEach((done) => { 15 beforeEach((done) => {
  16 + let articleService: any = helpers.mocks.articleService;
  17 + let blockService: any = jasmine.createSpyObj("blockService", ["getBlock"]);
14 let cls = createClass({ 18 let cls = createClass({
15 template: htmlTemplate, 19 template: htmlTemplate,
16 directives: [StatisticsBlockComponent], 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 properties: { 25 properties: {
19 block: { 26 block: {
20 statistics: [ 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 @Component({ 4 @Component({
4 selector: "noosfero-statistics-block", 5 selector: "noosfero-statistics-block",
5 templateUrl: 'app/layout/blocks/statistics/statistics-block.html' 6 templateUrl: 'app/layout/blocks/statistics/statistics-block.html'
6 }) 7 })
7 8
  9 +@Inject(ArticleService, BlockService)
8 export class StatisticsBlockComponent { 10 export class StatisticsBlockComponent {
9 @Input() block: noosfero.StatisticsBlock; 11 @Input() block: noosfero.StatisticsBlock;
10 @Input() owner: any; 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,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 \ No newline at end of file 0 \ No newline at end of file
src/app/shared/utils/hashmap.ts
@@ -1,36 +0,0 @@ @@ -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 \ No newline at end of file 0 \ No newline at end of file
src/app/shared/utils/index.ts
@@ -1,3 +0,0 @@ @@ -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,7 +23,7 @@ describe(&quot;Services&quot;, () =&gt; {
23 it("should remove article", (done) => { 23 it("should remove article", (done) => {
24 let articleId = 1; 24 let articleId = 1;
25 $httpBackend.expectDELETE(`/api/v1/articles/${articleId}`).respond(200, { success: "true" }); 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 $httpBackend.flush(); 27 $httpBackend.flush();
28 $httpBackend.verifyNoOutstandingExpectation(); 28 $httpBackend.verifyNoOutstandingExpectation();
29 done(); 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,8 +7,6 @@ import {NoosferoRootScope} from &quot;./../../../app/shared/models/interfaces&quot;;
7 @Inject("Restangular", "$q", "$log", ProfileService) 7 @Inject("Restangular", "$q", "$log", ProfileService)
8 export class ArticleService extends RestangularService<noosfero.Article> { 8 export class ArticleService extends RestangularService<noosfero.Article> {
9 9
10 - private articleRemoved: EventEmitter<noosfero.Article> = new EventEmitter<noosfero.Article>();  
11 -  
12 constructor(Restangular: restangular.IService, $q: ng.IQService, $log: ng.ILogService, protected profileService: ProfileService) { 10 constructor(Restangular: restangular.IService, $q: ng.IQService, $log: ng.ILogService, protected profileService: ProfileService) {
13 super(Restangular, $q, $log); 11 super(Restangular, $q, $log);
14 } 12 }
@@ -24,30 +22,22 @@ export class ArticleService extends RestangularService&lt;noosfero.Article&gt; { @@ -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 * Notify listeners that this article has been removed 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 updateArticle(article: noosfero.Article) { 42 updateArticle(article: noosfero.Article) {
53 let headers = { 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,4 +37,14 @@ export class BlockService extends RestangularService&lt;noosfero.Block&gt; {
37 return apiContentPromise.promise; 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 * @name RestangularService 3 * @name RestangularService
3 * Base class to be extended by classes which will provide access 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,6 +14,11 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; {
13 private baseResource: restangular.IElement; 14 private baseResource: restangular.IElement;
14 private currentPromise: ng.IDeferred<T>; 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 * Creates an instance of RestangularService. 23 * Creates an instance of RestangularService.
18 * 24 *
@@ -35,6 +41,22 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; { @@ -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 public resetCurrent() { 60 public resetCurrent() {
39 this.currentPromise = this.$q.defer(); 61 this.currentPromise = this.$q.defer();
40 } 62 }
@@ -107,7 +129,7 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; { @@ -107,7 +129,7 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; {
107 restRequest = this.restangularService.one(this.getResourcePath(), id).get(queryParams, headers); 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 .catch(this.getHandleErrorFunction(deferred)); 133 .catch(this.getHandleErrorFunction(deferred));
112 134
113 135
@@ -201,7 +223,7 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; { @@ -201,7 +223,7 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; {
201 restRequest = restangularObj.remove(queryParams, headers); 223 restRequest = restangularObj.remove(queryParams, headers);
202 224
203 restRequest 225 restRequest
204 - .then(this.getHandleSuccessFunction(deferred)) 226 + .then(this.getHandleSuccessFunction(deferred, this.modelRemovedEventEmitter))
205 .catch(this.getHandleErrorFunction(deferred)); 227 .catch(this.getHandleErrorFunction(deferred));
206 228
207 return deferred.promise; 229 return deferred.promise;
@@ -226,7 +248,7 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; { @@ -226,7 +248,7 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; {
226 248
227 restRequest = restangularObj.put(queryParams, headers); 249 restRequest = restangularObj.put(queryParams, headers);
228 250
229 - restRequest.then(this.getHandleSuccessFunction(deferred)) 251 + restRequest.then(this.getHandleSuccessFunction(deferred, this.modelUpdatedEventEmitter))
230 .catch(this.getHandleErrorFunction(deferred)); 252 .catch(this.getHandleErrorFunction(deferred));
231 253
232 return deferred.promise; 254 return deferred.promise;
@@ -255,7 +277,7 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; { @@ -255,7 +277,7 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; {
255 restRequest = this.baseResource.post(data, queryParams, headers); 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 .catch(this.getHandleErrorFunction(deferred)); 281 .catch(this.getHandleErrorFunction(deferred));
260 282
261 return deferred.promise; 283 return deferred.promise;
@@ -289,7 +311,7 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; { @@ -289,7 +311,7 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; {
289 } 311 }
290 312
291 /** HANDLERS */ 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 let self = this; 315 let self = this;
294 316
295 /** 317 /**
@@ -301,7 +323,13 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; { @@ -301,7 +323,13 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; {
301 if (self.$log) { 323 if (self.$log) {
302 self.$log.debug("Request successfull executed", response.data, self, response); 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 return successFunction; 334 return successFunction;
307 } 335 }
src/lib/util/arrays.ts 0 → 100644
@@ -0,0 +1,9 @@ @@ -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 \ No newline at end of file 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,6 +3,7 @@ import {Provider, Input, provide, Component} from &#39;ng-forward&#39;;
3 import {provideFilters} from '../../../../spec/helpers'; 3 import {provideFilters} from '../../../../spec/helpers';
4 import {DiscussionBlockComponent} from './discussion-block.component'; 4 import {DiscussionBlockComponent} from './discussion-block.component';
5 import {ComponentTestHelper, createClass} from './../../../../spec/component-test-helper'; 5 import {ComponentTestHelper, createClass} from './../../../../spec/component-test-helper';
  6 +import * as helpers from "./../../../../spec/helpers";
6 7
7 const htmlTemplate: string = '<noosfero-comment-paragraph-plugin-discussion-block [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-comment-paragraph-plugin-discussion-block>'; 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,11 +14,13 @@ describe(&quot;Components&quot;, () =&gt; {
13 14
14 let helper: ComponentTestHelper<DiscussionBlockComponent>; 15 let helper: ComponentTestHelper<DiscussionBlockComponent>;
15 let settingsObj = {}; 16 let settingsObj = {};
  17 + let article = <noosfero.Article>{ name: "article1" };
16 let mockedBlockService = { 18 let mockedBlockService = {
17 getApiContent: (content: any): any => { 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 let profile = { name: 'profile-name' }; 24 let profile = { name: 'profile-name' };
22 25
23 let state = jasmine.createSpyObj("state", ["go"]); 26 let state = jasmine.createSpyObj("state", ["go"]);
@@ -27,6 +30,7 @@ describe(&quot;Components&quot;, () =&gt; { @@ -27,6 +30,7 @@ describe(&quot;Components&quot;, () =&gt; {
27 new Provider('BlockService', { 30 new Provider('BlockService', {
28 useValue: mockedBlockService 31 useValue: mockedBlockService
29 }), 32 }),
  33 + new Provider('ArticleService', { useValue: articleService })
30 ].concat(provideFilters("truncateFilter", "stripTagsFilter", "translateFilter", "amDateFormatFilter")); 34 ].concat(provideFilters("truncateFilter", "stripTagsFilter", "translateFilter", "amDateFormatFilter"));
31 35
32 beforeEach(angular.mock.module("templates")); 36 beforeEach(angular.mock.module("templates"));
@@ -51,5 +55,17 @@ describe(&quot;Components&quot;, () =&gt; { @@ -51,5 +55,17 @@ describe(&quot;Components&quot;, () =&gt; {
51 block.openDocument({ path: "path", profile: { identifier: "identifier" } }); 55 block.openDocument({ path: "path", profile: { identifier: "identifier" } });
52 expect(state.go).toHaveBeenCalledWith("main.profile.page", { page: "path", profile: "identifier" }); 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 import {Component, Inject, Input} from "ng-forward"; 1 import {Component, Inject, Input} from "ng-forward";
2 import {BlockService} from "../../../../lib/ng-noosfero-api/http/block.service"; 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 @Component({ 6 @Component({
5 selector: "noosfero-comment-paragraph-plugin-discussion-block", 7 selector: "noosfero-comment-paragraph-plugin-discussion-block",
6 templateUrl: 'plugins/comment_paragraph/block/discussion/discussion-block.html' 8 templateUrl: 'plugins/comment_paragraph/block/discussion/discussion-block.html'
7 }) 9 })
8 -@Inject(BlockService, "$state") 10 +@Inject(BlockService, "$state", ArticleService)
9 export class DiscussionBlockComponent { 11 export class DiscussionBlockComponent {
10 12
11 @Input() block: any; 13 @Input() block: any;
@@ -14,7 +16,7 @@ export class DiscussionBlockComponent { @@ -14,7 +16,7 @@ export class DiscussionBlockComponent {
14 profile: noosfero.Profile; 16 profile: noosfero.Profile;
15 documents: Array<noosfero.Article>; 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 ngOnInit() { 21 ngOnInit() {
20 this.profile = this.owner; 22 this.profile = this.owner;
@@ -22,6 +24,13 @@ export class DiscussionBlockComponent { @@ -22,6 +24,13 @@ export class DiscussionBlockComponent {
22 this.documents = content.articles; 24 this.documents = content.articles;
23 this.block.hide = !this.documents || this.documents.length === 0; 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 openDocument(article: any) { 36 openDocument(article: any) {
src/spec/mocks.ts
@@ -77,10 +77,14 @@ export var mocks: any = { @@ -77,10 +77,14 @@ export var mocks: any = {
77 }, 77 },
78 articleService: { 78 articleService: {
79 articleRemovedFn: null, 79 articleRemovedFn: null,
80 - subscribeToArticleRemoved: (fn: Function) => { 80 + articleAddedFn: null,
  81 + subscribeToModelRemoved: (fn: Function) => {
81 mocks.articleService.articleRemovedFn = fn; 82 mocks.articleService.articleRemovedFn = fn;
82 }, 83 },
83 - articleRemoved: 84 + subscribeToModelAdded: (fn: Function) => {
  85 + mocks.articleService.articleAddedFn = fn;
  86 + },
  87 + modelRemovedEventEmitter:
84 { 88 {
85 subscribe: (fn: Function) => { 89 subscribe: (fn: Function) => {
86 mocks.articleService.articleRemovedFn = fn; 90 mocks.articleService.articleRemovedFn = fn;
@@ -90,18 +94,22 @@ export var mocks: any = { @@ -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 return { 108 return {
95 catch: (func?: Function) => { 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 getByProfile: (profileId: number, params?: any) => { 113 getByProfile: (profileId: number, params?: any) => {
106 return { 114 return {
107 then: (func?: Function) => { 115 then: (func?: Function) => {