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 9 let articleServiceMock: any;
10 10 let profileServiceMock: any;
11 11 let $state: any;
  12 + let $stateParams: any;
12 13 let profile = { id: 1 };
13 14 let notification: any;
14 15  
... ... @@ -20,9 +21,10 @@ describe("Article BasicEditor", () => {
20 21  
21 22 beforeEach(() => {
22 23 $state = jasmine.createSpyObj("$state", ["transitionTo"]);
  24 + $stateParams = jasmine.createSpyObj("$stateParams", ["parent_id"]);
23 25 notification = jasmine.createSpyObj("notification", ["success"]);
24 26 profileServiceMock = jasmine.createSpyObj("profileServiceMock", ["getCurrentProfile"]);
25   - articleServiceMock = jasmine.createSpyObj("articleServiceMock", ["createInProfile"]);
  27 + articleServiceMock = jasmine.createSpyObj("articleServiceMock", ["createInParent"]);
26 28  
27 29 let getCurrentProfileResponse = $q.defer();
28 30 getCurrentProfileResponse.resolve(profile);
... ... @@ -31,20 +33,20 @@ describe("Article BasicEditor", () => {
31 33 articleCreate.resolve({ data: { path: "path", profile: { identifier: "profile" } } });
32 34  
33 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 39 it("create an article in the current profile when save", done => {
38   - let component: BasicEditorComponent = new BasicEditorComponent(articleServiceMock, profileServiceMock, $state, notification);
3
  • 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
  • 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

    Sure! It's ok for me :)

    Choose File ...   File name...
    Cancel
  40 + let component: BasicEditorComponent = new BasicEditorComponent(articleServiceMock, profileServiceMock, $state, notification, $stateParams);
39 41 component.save();
40 42 $rootScope.$apply();
41 43 expect(profileServiceMock.getCurrentProfile).toHaveBeenCalled();
42   - expect(articleServiceMock.createInProfile).toHaveBeenCalledWith(profile, component.article);
  44 + expect(articleServiceMock.createInParent).toHaveBeenCalledWith($stateParams.parent_id, component.article);
43 45 done();
44 46 });
45 47  
46 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 50 component.save();
49 51 $rootScope.$apply();
50 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 12 provide('notification', { useClass: NotificationService })
13 13 ]
14 14 })
15   -@Inject(ArticleService, ProfileService, "$state", NotificationService)
  15 +@Inject(ArticleService, ProfileService, "$state", NotificationService, "$stateParams")
16 16 export class BasicEditorComponent {
17 17  
18 18 article: noosfero.Article = <noosfero.Article>{};
  19 + parentId: number;
  20 +
  21 + editorOptions = {};
19 22  
20 23 constructor(private articleService: ArticleService,
21 24 private profileService: ProfileService,
22 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 31 save() {
26 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 34 }).then((response: noosfero.RestResult<noosfero.Article>) => {
29 35 let article = (<noosfero.Article>response.data);
30 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 20 return <any>[
21 21 provide('ProfileService', {
22 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 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
  • Me
    Michel Felipe @mfdeveloper

    Why did you not use ComponentTestHelper class?

    Choose File ...   File name...
    Cancel
  • 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
  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 77 it('check if profile was loaded', (done: Function) => {
48 78 let profile: any = {
49 79 id: 1,
... ...
src/app/article/content-viewer/content-viewer-actions.component.ts
1 1 import {Component, Inject, provide} from "ng-forward";
2 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 5 @Component({
5 6 selector: "content-viewer-actions",
6 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

    It is necessary? Seems redundant for me, no?

    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

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

    Choose File ...   File name...
    Cancel
  10 + provide('articleService', { useClass: ArticleService })
  11 + ]
8 12 })
9   -@Inject(ProfileService)
  13 +@Inject(ProfileService, ArticleService)
10 14 export class ContentViewerActionsComponent {
11 15  
12 16 article: noosfero.Article;
13 17 profile: noosfero.Profile;
  18 + parentId: number;
14 19  
15   - constructor(profileService: ProfileService) {
  20 + constructor(profileService: ProfileService, articleService: ArticleService) {
16 21 profileService.getCurrentProfile().then((profile: noosfero.Profile) => {
17 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 28 }
29 29  
30 30 activate() {
31   - this.profileService.getCurrentProfile().then((profile: noosfero.Profile) => {
  31 + this.profileService.getCurrentProfile().then((profile: noosfero.Profile) => {
32 32 this.profile = profile;
33 33 return this.articleService.getArticleByProfileAndPath(this.profile, this.$stateParams["page"]);
34 34 }).then((result: noosfero.RestResult<any>) => {
35 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 1 <ul class="nav navbar-nav">
2 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 4 <i class="fa fa-file fa-fw fa-lg"></i> {{"navbar.content_viewer_actions.new_post" | translate}}
5 5 </a>
6 6 </li>
... ...
src/app/profile/profile.component.ts
... ... @@ -46,7 +46,7 @@ import {MyProfileComponent} from &quot;./myprofile.component&quot;;
46 46 },
47 47 {
48 48 name: 'main.profile.cms',
49   - url: "^/myprofile/:profile/cms",
  49 + url: "^/myprofile/:profile/cms?parent_id",
50 50 component: BasicEditorComponent,
51 51 views: {
52 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 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 44 getAsCollectionChildrenOf<C>(rootElement: noosfero.Environment | noosfero.Article | noosfero.Profile, path: string, queryParams?: any, headers?: any): restangular.ICollectionPromise<C> {
37 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 236 * Creates a new Resource into the resource collection
237 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 240 let deferred = this.$q.defer<noosfero.RestResult<T>>();
241 241  
242 242 let restRequest: ng.IPromise<noosfero.RestResult<T>>;
... ... @@ -248,8 +248,9 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; {
248 248 data = obj;
249 249 }
250 250  
  251 + let subpath = path || this.getResourcePath();
251 252 if (rootElement) {
252   - restRequest = rootElement.all(this.getResourcePath()).post(data, queryParams, headers);
  253 + restRequest = rootElement.all(subpath).post(data, queryParams, headers);
253 254 } else {
254 255 restRequest = this.baseResource.post(data, queryParams, headers);
255 256 }
... ...
src/lib/ng-noosfero-api/interfaces/article.ts
1 1  
2 2 namespace noosfero {
3   - export interface Article extends RestModel {
  3 + export interface Article extends RestModel {
4 4 path: string;
5 5 profile: Profile;
6 6 type: string;
  7 + parent: Article;
7 8 }
8   -}
9 9 \ No newline at end of file
  10 +}
... ...
src/spec/mocks.ts
1 1 const DEBUG = false;
2 2  
3   -let log = (message: string, ...args: any[]) => {
  3 +let log = (message: string, ...args: any[]) => {
4 4 if (DEBUG) {
5 5 console.log(message);
6 6 }
... ... @@ -70,7 +70,9 @@ export var mocks = {
70 70 return {
71 71 then: (func?: Function) => { if (func) func(); }
72 72 };
73   - }
  73 + },
  74 + setCurrent: (article: noosfero.Article) => { },
  75 + getCurrent: () => { return Promise.resolve({}); }
74 76 },
75 77 environmentService: {
76 78 getEnvironmentPeople: (params: any) => {
... ...