Commit 2259690d92c4ced0e7a5a8a08be67b27d8112932

Authored by Victor Costa
1 parent 28d86107

Create article and associate it to parent

src/app/article/basic-editor.component.spec.ts
@@ -9,6 +9,7 @@ describe("Article BasicEditor", () => { @@ -9,6 +9,7 @@ describe("Article BasicEditor", () => {
9 let articleServiceMock: any; 9 let articleServiceMock: any;
10 let profileServiceMock: any; 10 let profileServiceMock: any;
11 let $state: any; 11 let $state: any;
  12 + let $stateParams: any;
12 let profile = { id: 1 }; 13 let profile = { id: 1 };
13 let notification: any; 14 let notification: any;
14 15
@@ -20,9 +21,10 @@ describe("Article BasicEditor", () => { @@ -20,9 +21,10 @@ describe("Article BasicEditor", () => {
20 21
21 beforeEach(() => { 22 beforeEach(() => {
22 $state = jasmine.createSpyObj("$state", ["transitionTo"]); 23 $state = jasmine.createSpyObj("$state", ["transitionTo"]);
  24 + $stateParams = jasmine.createSpyObj("$stateParams", ["parent_id"]);
23 notification = jasmine.createSpyObj("notification", ["success"]); 25 notification = jasmine.createSpyObj("notification", ["success"]);
24 profileServiceMock = jasmine.createSpyObj("profileServiceMock", ["getCurrentProfile"]); 26 profileServiceMock = jasmine.createSpyObj("profileServiceMock", ["getCurrentProfile"]);
25 - articleServiceMock = jasmine.createSpyObj("articleServiceMock", ["createInProfile"]); 27 + articleServiceMock = jasmine.createSpyObj("articleServiceMock", ["createInParent"]);
26 28
27 let getCurrentProfileResponse = $q.defer(); 29 let getCurrentProfileResponse = $q.defer();
28 getCurrentProfileResponse.resolve(profile); 30 getCurrentProfileResponse.resolve(profile);
@@ -31,20 +33,20 @@ describe("Article BasicEditor", () => { @@ -31,20 +33,20 @@ describe("Article BasicEditor", () => {
31 articleCreate.resolve({ data: { path: "path", profile: { identifier: "profile" } } }); 33 articleCreate.resolve({ data: { path: "path", profile: { identifier: "profile" } } });
32 34
33 profileServiceMock.getCurrentProfile = jasmine.createSpy("getCurrentProfile").and.returnValue(getCurrentProfileResponse.promise); 35 profileServiceMock.getCurrentProfile = jasmine.createSpy("getCurrentProfile").and.returnValue(getCurrentProfileResponse.promise);
34 - articleServiceMock.createInProfile = jasmine.createSpy("createInProfile").and.returnValue(articleCreate.promise); 36 + articleServiceMock.createInParent = jasmine.createSpy("createInParent").and.returnValue(articleCreate.promise);
35 }); 37 });
36 38
37 it("create an article in the current profile when save", done => { 39 it("create an article in the current profile when save", done => {
38 - let component: BasicEditorComponent = new BasicEditorComponent(articleServiceMock, profileServiceMock, $state, notification); 40 + let component: BasicEditorComponent = new BasicEditorComponent(articleServiceMock, profileServiceMock, $state, notification, $stateParams);
3
  • Me
    Michel Felipe @mfdeveloper

    Sure! It's ok for me :)

    Choose File ...   File name...
    Cancel
  • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
    Victor Costa @vfcosta

    It is a legacy code, I just include the missing parameter to fix the test.

    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper

    Why did you instantiate the BasicEditorComponent "by hand" here? What do you think about usage of the ComponentTestHelper to more clean unit specs?

    Choose File ...   File name...
    Cancel
39 component.save(); 41 component.save();
40 $rootScope.$apply(); 42 $rootScope.$apply();
41 expect(profileServiceMock.getCurrentProfile).toHaveBeenCalled(); 43 expect(profileServiceMock.getCurrentProfile).toHaveBeenCalled();
42 - expect(articleServiceMock.createInProfile).toHaveBeenCalledWith(profile, component.article); 44 + expect(articleServiceMock.createInParent).toHaveBeenCalledWith($stateParams.parent_id, component.article);
43 done(); 45 done();
44 }); 46 });
45 47
46 it("got to the new article page and display an alert when saving sucessfully", done => { 48 it("got to the new article page and display an alert when saving sucessfully", done => {
47 - let component: BasicEditorComponent = new BasicEditorComponent(articleServiceMock, profileServiceMock, $state, notification); 49 + let component: BasicEditorComponent = new BasicEditorComponent(articleServiceMock, profileServiceMock, $state, notification, $stateParams);
48 component.save(); 50 component.save();
49 $rootScope.$apply(); 51 $rootScope.$apply();
50 expect($state.transitionTo).toHaveBeenCalledWith("main.profile.page", { page: "path", profile: "profile" }); 52 expect($state.transitionTo).toHaveBeenCalledWith("main.profile.page", { page: "path", profile: "profile" });
src/app/article/basic-editor.component.ts
@@ -12,19 +12,25 @@ import {NotificationService} from "../shared/services/notification.service.ts"; @@ -12,19 +12,25 @@ import {NotificationService} from "../shared/services/notification.service.ts";
12 provide('notification', { useClass: NotificationService }) 12 provide('notification', { useClass: NotificationService })
13 ] 13 ]
14 }) 14 })
15 -@Inject(ArticleService, ProfileService, "$state", NotificationService) 15 +@Inject(ArticleService, ProfileService, "$state", NotificationService, "$stateParams")
16 export class BasicEditorComponent { 16 export class BasicEditorComponent {
17 17
18 article: noosfero.Article = <noosfero.Article>{}; 18 article: noosfero.Article = <noosfero.Article>{};
  19 + parentId: number;
  20 +
  21 + editorOptions = {};
19 22
20 constructor(private articleService: ArticleService, 23 constructor(private articleService: ArticleService,
21 private profileService: ProfileService, 24 private profileService: ProfileService,
22 private $state: ng.ui.IStateService, 25 private $state: ng.ui.IStateService,
23 - private notification: NotificationService) { } 26 + private notification: NotificationService,
  27 + private $stateParams: ng.ui.IStateParamsService) {
  28 + this.parentId = this.$stateParams['parent_id'];
  29 + }
24 30
25 save() { 31 save() {
26 this.profileService.getCurrentProfile().then((profile: noosfero.Profile) => { 32 this.profileService.getCurrentProfile().then((profile: noosfero.Profile) => {
27 - return this.articleService.createInProfile(profile, this.article); 33 + return this.articleService.createInParent(this.parentId, this.article);
28 }).then((response: noosfero.RestResult<noosfero.Article>) => { 34 }).then((response: noosfero.RestResult<noosfero.Article>) => {
29 let article = (<noosfero.Article>response.data); 35 let article = (<noosfero.Article>response.data);
30 this.$state.transitionTo('main.profile.page', { page: article.path, profile: article.profile.identifier }); 36 this.$state.transitionTo('main.profile.page', { page: article.path, profile: article.profile.identifier });
src/app/article/content-viewer/content-viewer-actions.component.spec.ts
@@ -20,6 +20,9 @@ describe(&#39;Content Viewer Actions Component&#39;, () =&gt; { @@ -20,6 +20,9 @@ describe(&#39;Content Viewer Actions Component&#39;, () =&gt; {
20 return <any>[ 20 return <any>[
21 provide('ProfileService', { 21 provide('ProfileService', {
22 useValue: helpers.mocks.profileService 22 useValue: helpers.mocks.profileService
  23 + }),
  24 + provide('ArticleService', {
  25 + useValue: helpers.mocks.articleService
23 }) 26 })
24 ]; 27 ];
25 }); 28 });
@@ -44,6 +47,33 @@ describe(&#39;Content Viewer Actions Component&#39;, () =&gt; { @@ -44,6 +47,33 @@ describe(&#39;Content Viewer Actions Component&#39;, () =&gt; {
44 }); 47 });
45 }); 48 });
46 49
  50 + it('return article parent as container when it is not a folder', (done: Function) => {
  51 + buildComponent().then((fixture: ComponentFixture) => {
  52 + let component = fixture.debugElement.componentViewChildren[0].componentInstance;
2
  • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
    Victor Costa @vfcosta

    It's a legacy code, I just move this file to another folder. I'll refactor it and apply your considerations.

    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper

    Why did you not use ComponentTestHelper class?

    Choose File ...   File name...
    Cancel
  53 + let article = <noosfero.Article>({ id: 1, type: 'TextArticle', parent: { id: 2 } });
  54 + expect(component.getArticleContainer(article)).toEqual(2);
  55 + done();
  56 + });
  57 + });
  58 +
  59 + it('return article as container when it is a folder', (done: Function) => {
  60 + buildComponent().then((fixture: ComponentFixture) => {
  61 + let component = fixture.debugElement.componentViewChildren[0].componentInstance;
  62 + let article = <noosfero.Article>({ id: 1, type: 'Folder' });
  63 + expect(component.getArticleContainer(article)).toEqual(1);
  64 + done();
  65 + });
  66 + });
  67 +
  68 + it('return article as container when it is a blog', (done: Function) => {
  69 + buildComponent().then((fixture: ComponentFixture) => {
  70 + let component = fixture.debugElement.componentViewChildren[0].componentInstance;
  71 + let article = <noosfero.Article>({ id: 1, type: 'Blog' });
  72 + expect(component.getArticleContainer(article)).toEqual(1);
  73 + done();
  74 + });
  75 + });
  76 +
47 it('check if profile was loaded', (done: Function) => { 77 it('check if profile was loaded', (done: Function) => {
48 let profile: any = { 78 let profile: any = {
49 id: 1, 79 id: 1,
src/app/article/content-viewer/content-viewer-actions.component.ts
1 import {Component, Inject, provide} from "ng-forward"; 1 import {Component, Inject, provide} from "ng-forward";
2 import {ProfileService} from "../../../lib/ng-noosfero-api/http/profile.service"; 2 import {ProfileService} from "../../../lib/ng-noosfero-api/http/profile.service";
  3 +import {ArticleService} from "../../../lib/ng-noosfero-api/http/article.service";
3 4
4 @Component({ 5 @Component({
5 selector: "content-viewer-actions", 6 selector: "content-viewer-actions",
6 templateUrl: "app/article/content-viewer/navbar-actions.html", 7 templateUrl: "app/article/content-viewer/navbar-actions.html",
7 - providers: [provide('profileService', { useClass: ProfileService })] 8 + providers: [
  9 + provide('profileService', { useClass: ProfileService }),
3
  • Me
    Michel Felipe @mfdeveloper

    I understand now. This is a ng-foward constraint?

    Choose File ...   File name...
    Cancel
  • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
    Victor Costa @vfcosta

    Sadly yes. The alternative here is to use ProfileService as the parameter name. However, it works seamlessly in components that don't uses ui-roter directly (e.g. members-block.component.ts).

    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper

    It is necessary? Seems redundant for me, no?

    Choose File ...   File name...
    Cancel
  10 + provide('articleService', { useClass: ArticleService })
  11 + ]
8 }) 12 })
9 -@Inject(ProfileService) 13 +@Inject(ProfileService, ArticleService)
10 export class ContentViewerActionsComponent { 14 export class ContentViewerActionsComponent {
11 15
12 article: noosfero.Article; 16 article: noosfero.Article;
13 profile: noosfero.Profile; 17 profile: noosfero.Profile;
  18 + parentId: number;
14 19
15 - constructor(profileService: ProfileService) { 20 + constructor(profileService: ProfileService, articleService: ArticleService) {
16 profileService.getCurrentProfile().then((profile: noosfero.Profile) => { 21 profileService.getCurrentProfile().then((profile: noosfero.Profile) => {
17 this.profile = profile; 22 this.profile = profile;
  23 + return articleService.getCurrent();
  24 + }).then((article: noosfero.Article) => {
  25 + this.article = article;
  26 + this.parentId = this.getArticleContainer(article);
18 }); 27 });
19 } 28 }
  29 +
  30 + getArticleContainer(article: noosfero.Article) {
  31 + // FIXME get folder types from api
  32 + if (article.type === "Blog" || article.type === "Folder") {
  33 + return article.id;
  34 + } else if (article.parent) {
  35 + return article.parent.id;
  36 + }
  37 + }
20 } 38 }
src/app/article/content-viewer/content-viewer.component.ts
@@ -28,11 +28,12 @@ export class ContentViewerComponent { @@ -28,11 +28,12 @@ export class ContentViewerComponent {
28 } 28 }
29 29
30 activate() { 30 activate() {
31 - this.profileService.getCurrentProfile().then((profile: noosfero.Profile) => { 31 + this.profileService.getCurrentProfile().then((profile: noosfero.Profile) => {
32 this.profile = profile; 32 this.profile = profile;
33 return this.articleService.getArticleByProfileAndPath(this.profile, this.$stateParams["page"]); 33 return this.articleService.getArticleByProfileAndPath(this.profile, this.$stateParams["page"]);
34 }).then((result: noosfero.RestResult<any>) => { 34 }).then((result: noosfero.RestResult<any>) => {
35 this.article = <noosfero.Article>result.data; 35 this.article = <noosfero.Article>result.data;
  36 + this.articleService.setCurrent(this.article);
36 }); 37 });
37 } 38 }
38 } 39 }
src/app/article/content-viewer/navbar-actions.html
1 <ul class="nav navbar-nav"> 1 <ul class="nav navbar-nav">
2 <li ng-show="vm.profile"> 2 <li ng-show="vm.profile">
3 - <a href="#" role="button" ui-sref="main.profile.cms({profile: vm.profile.identifier})"> 3 + <a ng-show="vm.parentId" href="#" role="button" ui-sref="main.profile.cms({profile: vm.profile.identifier, parent_id: vm.parentId})">
4 <i class="fa fa-file fa-fw fa-lg"></i> {{"navbar.content_viewer_actions.new_post" | translate}} 4 <i class="fa fa-file fa-fw fa-lg"></i> {{"navbar.content_viewer_actions.new_post" | translate}}
5 </a> 5 </a>
6 </li> 6 </li>
src/app/profile/profile.component.ts
@@ -46,7 +46,7 @@ import {MyProfileComponent} from &quot;./myprofile.component&quot;; @@ -46,7 +46,7 @@ import {MyProfileComponent} from &quot;./myprofile.component&quot;;
46 }, 46 },
47 { 47 {
48 name: 'main.profile.cms', 48 name: 'main.profile.cms',
49 - url: "^/myprofile/:profile/cms", 49 + url: "^/myprofile/:profile/cms?parent_id",
50 component: BasicEditorComponent, 50 component: BasicEditorComponent,
51 views: { 51 views: {
52 "mainBlockContent": { 52 "mainBlockContent": {
src/lib/ng-noosfero-api/http/article.service.ts
@@ -32,6 +32,14 @@ export class ArticleService extends RestangularService&lt;noosfero.Article&gt; { @@ -32,6 +32,14 @@ export class ArticleService extends RestangularService&lt;noosfero.Article&gt; {
32 return this.create(article, <noosfero.RestModel>profileElement, null, headers); 32 return this.create(article, <noosfero.RestModel>profileElement, null, headers);
33 } 33 }
34 34
  35 + createInParent(parentId: number, article: noosfero.Article): ng.IPromise<noosfero.RestResult<noosfero.Article>> {
  36 + let headers = {
  37 + 'Content-Type': 'application/json'
  38 + };
  39 +
  40 + let parent = this.getElement(parentId);
  41 + return this.create(article, parent, null, headers, true, "children");
  42 + }
35 43
36 getAsCollectionChildrenOf<C>(rootElement: noosfero.Environment | noosfero.Article | noosfero.Profile, path: string, queryParams?: any, headers?: any): restangular.ICollectionPromise<C> { 44 getAsCollectionChildrenOf<C>(rootElement: noosfero.Environment | noosfero.Article | noosfero.Profile, path: string, queryParams?: any, headers?: any): restangular.ICollectionPromise<C> {
37 return rootElement.getList<C>(path, queryParams, headers); 45 return rootElement.getList<C>(path, queryParams, headers);
src/lib/ng-noosfero-api/http/restangular_service.ts
@@ -236,7 +236,7 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; { @@ -236,7 +236,7 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; {
236 * Creates a new Resource into the resource collection 236 * Creates a new Resource into the resource collection
237 * calls POST /resourcePath 237 * calls POST /resourcePath
238 */ 238 */
239 - public create(obj: T, rootElement?: noosfero.RestModel, queryParams?: any, headers?: any, isSub: boolean = true): ng.IPromise<noosfero.RestResult<T>> { 239 + public create(obj: T, rootElement?: noosfero.RestModel, queryParams?: any, headers?: any, isSub: boolean = true, path?: string): ng.IPromise<noosfero.RestResult<T>> {
240 let deferred = this.$q.defer<noosfero.RestResult<T>>(); 240 let deferred = this.$q.defer<noosfero.RestResult<T>>();
241 241
242 let restRequest: ng.IPromise<noosfero.RestResult<T>>; 242 let restRequest: ng.IPromise<noosfero.RestResult<T>>;
@@ -248,8 +248,9 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; { @@ -248,8 +248,9 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; {
248 data = obj; 248 data = obj;
249 } 249 }
250 250
  251 + let subpath = path || this.getResourcePath();
251 if (rootElement) { 252 if (rootElement) {
252 - restRequest = rootElement.all(this.getResourcePath()).post(data, queryParams, headers); 253 + restRequest = rootElement.all(subpath).post(data, queryParams, headers);
253 } else { 254 } else {
254 restRequest = this.baseResource.post(data, queryParams, headers); 255 restRequest = this.baseResource.post(data, queryParams, headers);
255 } 256 }
src/lib/ng-noosfero-api/interfaces/article.ts
1 1
2 namespace noosfero { 2 namespace noosfero {
3 - export interface Article extends RestModel { 3 + export interface Article extends RestModel {
4 path: string; 4 path: string;
5 profile: Profile; 5 profile: Profile;
6 type: string; 6 type: string;
  7 + parent: Article;
7 } 8 }
8 -}  
9 \ No newline at end of file 9 \ No newline at end of file
  10 +}
src/spec/mocks.ts
1 const DEBUG = false; 1 const DEBUG = false;
2 2
3 -let log = (message: string, ...args: any[]) => { 3 +let log = (message: string, ...args: any[]) => {
4 if (DEBUG) { 4 if (DEBUG) {
5 console.log(message); 5 console.log(message);
6 } 6 }
@@ -70,7 +70,9 @@ export var mocks = { @@ -70,7 +70,9 @@ export var mocks = {
70 return { 70 return {
71 then: (func?: Function) => { if (func) func(); } 71 then: (func?: Function) => { if (func) func(); }
72 }; 72 };
73 - } 73 + },
  74 + setCurrent: (article: noosfero.Article) => { },
  75 + getCurrent: () => { return Promise.resolve({}); }
74 }, 76 },
75 environmentService: { 77 environmentService: {
76 getEnvironmentPeople: (params: any) => { 78 getEnvironmentPeople: (params: any) => {