diff --git a/src/app/cms/cms.component.spec.ts b/src/app/cms/cms.component.spec.ts index 605bf91..adca29f 100644 --- a/src/app/cms/cms.component.spec.ts +++ b/src/app/cms/cms.component.spec.ts @@ -9,7 +9,7 @@ describe("Components", () => { let articleServiceMock: any; let profileServiceMock: any; let $state: any; - let sweetAlert: any; + let notification: any; beforeEach(inject((_$rootScope_: ng.IRootScopeService, _$q_: ng.IQService) => { $rootScope = _$rootScope_; @@ -18,7 +18,7 @@ describe("Components", () => { beforeEach(() => { $state = jasmine.createSpyObj("$state", ["transitionTo"]); - sweetAlert = jasmine.createSpyObj("SweetAlert", ["swal"]); + notification = jasmine.createSpyObj("notification", ["success"]); profileServiceMock = jasmine.createSpyObj("profileServiceMock", ["getCurrentProfile"]); articleServiceMock = jasmine.createSpyObj("articleServiceMock", ["create"]); @@ -33,7 +33,7 @@ describe("Components", () => { }); it("create an article in the current profile when save", done => { - let component: Cms = new Cms(articleServiceMock, profileServiceMock, $state, sweetAlert); + let component: Cms = new Cms(articleServiceMock, profileServiceMock, $state, notification); component.save(); $rootScope.$apply(); expect(profileServiceMock.getCurrentProfile).toHaveBeenCalled(); @@ -42,11 +42,11 @@ describe("Components", () => { }); it("got to the new article page and display an alert when saving sucessfully", done => { - let component: Cms = new Cms(articleServiceMock, profileServiceMock, $state, sweetAlert); + let component: Cms = new Cms(articleServiceMock, profileServiceMock, $state, notification); component.save(); $rootScope.$apply(); expect($state.transitionTo).toHaveBeenCalledWith("main.profile.page", { page: "path", profile: "profile" }); - expect(sweetAlert.swal).toHaveBeenCalled(); + expect(notification.success).toHaveBeenCalled(); done(); }); diff --git a/src/app/cms/cms.component.ts b/src/app/cms/cms.component.ts index 4dad48b..e82911a 100644 --- a/src/app/cms/cms.component.ts +++ b/src/app/cms/cms.component.ts @@ -2,35 +2,33 @@ import {StateConfig, Component, Inject, provide} from 'ng-forward'; import {Profile} from "./../models/interfaces"; import {ArticleService} from "../../lib/ng-noosfero-api/http/article.service"; import {ProfileService} from "../../lib/ng-noosfero-api/http/profile.service"; +import {Notification} from "../components/notification/notification.component"; @Component({ selector: 'cms', templateUrl: "app/cms/cms.html", providers: [ provide('articleService', { useClass: ArticleService }), - provide('profileService', { useClass: ProfileService }) + provide('profileService', { useClass: ProfileService }), + provide('notification', { useClass: Notification }) ] }) -@Inject(ArticleService, ProfileService, "$state", "SweetAlert") +@Inject(ArticleService, ProfileService, "$state", Notification) export class Cms { article: any = {}; constructor(private articleService: ArticleService, private profileService: ProfileService, - private $state: ng.ui.IStateService, private SweetAlert: any) { } + private $state: ng.ui.IStateService, + private notification: Notification) { } save() { this.profileService.getCurrentProfile().then((profile: Profile) => { return this.articleService.create(profile.id, this.article); }).then((response: restangular.IResponse) => { this.$state.transitionTo('main.profile.page', { page: response.data.article.path, profile: response.data.article.profile.identifier }); - this.SweetAlert.swal({ - title: "Good job!", - text: "Article saved!", - type: "success", - timer: 1000 - }); + this.notification.success("Good job!", "Article saved!"); }); } diff --git a/src/app/components/notification/notification.component.spec.ts b/src/app/components/notification/notification.component.spec.ts new file mode 100644 index 0000000..cb5f881 --- /dev/null +++ b/src/app/components/notification/notification.component.spec.ts @@ -0,0 +1,67 @@ +import {TestComponentBuilder, ComponentFixture} from 'ng-forward/cjs/testing/test-component-builder'; +import {Pipe, Input, provide, Component} from 'ng-forward'; + +import * as helpers from "../../../spec/helpers"; + +import {Notification} from "./notification.component"; + +const tcb = new TestComponentBuilder(); + +describe("Components", () => { + + describe("Profile Image Component", () => { + + beforeEach(angular.mock.module("templates")); + + it("use the default message when call notification component without a specific message", done => { + let sweetAlert = jasmine.createSpyObj("sweetAlert", ["swal"]); + sweetAlert.swal = jasmine.createSpy("swal"); + + let component: Notification = new Notification(helpers.mocks.$log, sweetAlert); + component.httpError(500, {}); + expect(sweetAlert.swal).toHaveBeenCalledWith(jasmine.objectContaining({ + text: Notification.DEFAULT_HTTP_ERROR_MESSAGE, + type: "error" + })); + done(); + }); + + it("use the default message when call notification component without error data", done => { + let sweetAlert = jasmine.createSpyObj("sweetAlert", ["swal"]); + sweetAlert.swal = jasmine.createSpy("swal"); + + let component: Notification = new Notification(helpers.mocks.$log, sweetAlert); + component.httpError(500, null); + expect(sweetAlert.swal).toHaveBeenCalledWith(jasmine.objectContaining({ + text: Notification.DEFAULT_HTTP_ERROR_MESSAGE, + type: "error" + })); + done(); + }); + + it("display a success message when call notification success", done => { + let sweetAlert = jasmine.createSpyObj("sweetAlert", ["swal"]); + sweetAlert.swal = jasmine.createSpy("swal"); + + let component: Notification = new Notification(helpers.mocks.$log, sweetAlert); + component.success("title", "message", 1000); + expect(sweetAlert.swal).toHaveBeenCalledWith(jasmine.objectContaining({ + type: "success" + })); + done(); + }); + + it("set the default timer in success messages", done => { + let sweetAlert = jasmine.createSpyObj("sweetAlert", ["swal"]); + sweetAlert.swal = jasmine.createSpy("swal"); + + let component: Notification = new Notification(helpers.mocks.$log, sweetAlert); + component.success("title", "message"); + expect(sweetAlert.swal).toHaveBeenCalledWith(jasmine.objectContaining({ + type: "success", + timer: Notification.DEFAULT_SUCCESS_TIMER + })); + done(); + }); + }); +}); diff --git a/src/app/components/notification/notification.component.ts b/src/app/components/notification/notification.component.ts new file mode 100644 index 0000000..bdae482 --- /dev/null +++ b/src/app/components/notification/notification.component.ts @@ -0,0 +1,33 @@ +import {Injectable, Inject} from "ng-forward"; + +@Injectable() +@Inject("$log", "SweetAlert") +export class Notification { + + constructor(private $log: ng.ILogService, private SweetAlert: any) { } + + public static DEFAULT_HTTP_ERROR_MESSAGE = "Something went wrong!"; + public static DEFAULT_SUCCESS_TIMER = 1000; + + httpError(status: number, data: any): boolean { + this.$log.debug(status, data); + + let message = (data || {}).message || Notification.DEFAULT_HTTP_ERROR_MESSAGE; + this.SweetAlert.swal({ + title: "Oops...", + text: message, + type: "error" + }); + return true; // return true to indicate that the error was already handled + } + + success(title: string, text: string, timer: number = Notification.DEFAULT_SUCCESS_TIMER) { + this.SweetAlert.swal({ + title: title, + text: text, + type: "success", + timer: timer + }); + } + +} diff --git a/src/app/index.run.ts b/src/app/index.run.ts index e23172f..92cf02c 100644 --- a/src/app/index.run.ts +++ b/src/app/index.run.ts @@ -1,12 +1,22 @@ import {Session} from "./components/auth/session"; +import {Notification} from "./components/notification/notification.component"; /** @ngInject */ -export function noosferoAngularRunBlock($log: ng.ILogService, Restangular: restangular.IService, Session: Session) { - Restangular.addFullRequestInterceptor((element: any, operation: string, route: string, url: string, headers: string) => { +export function noosferoAngularRunBlock( + $log: ng.ILogService, + Restangular: restangular.IService, + Session: Session, + Notification: Notification +) { + + Restangular.addFullRequestInterceptor((element: any, operation: string, route: string, url: string, headers: string) => { if (Session.currentUser()) { (headers)["Private-Token"] = Session.currentUser().private_token; } return { headers: headers }; }); + Restangular.setErrorInterceptor((response: restangular.IResponse, deferred: ng.IDeferred) => { + // return false to break the promise chain and don't call catch + return !Notification.httpError(response.status, response.data); + }); } - diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index b4806e9..87a00ee 100644 --- a/src/app/main/main.component.ts +++ b/src/app/main/main.component.ts @@ -17,6 +17,7 @@ import {DateFormat} from "../components/noosfero/date-format/date-format.filter" import {AuthService} from "./../components/auth/auth_service"; import {Session} from "./../components/auth/session"; +import {Notification} from "./../components/notification/notification.component"; import {Navbar} from "../components/navbar/navbar"; @@ -41,7 +42,7 @@ export class MainContent { MainBlock, RecentDocumentsBlock, Navbar, ProfileImageBlock, MembersBlock, NoosferoTemplate, DateFormat, RawHTMLBlock ], - providers: [AuthService, Session] + providers: [AuthService, Session, Notification] }) @StateConfig([ { diff --git a/src/app/profile/profile.component.spec.ts b/src/app/profile/profile.component.spec.ts index 2cf44ce..330d357 100644 --- a/src/app/profile/profile.component.spec.ts +++ b/src/app/profile/profile.component.spec.ts @@ -7,6 +7,7 @@ describe("Components", () => { let $rootScope: ng.IRootScopeService; let $q: ng.IQService; let profileServiceMock: any; + let notificationMock: any; let $stateParams: any; beforeEach(inject((_$rootScope_: ng.IRootScopeService, _$q_: ng.IQService) => { @@ -17,6 +18,7 @@ describe("Components", () => { beforeEach(() => { $stateParams = jasmine.createSpyObj("$stateParams", ["profile"]); profileServiceMock = jasmine.createSpyObj("profileServiceMock", ["setCurrentProfileByIdentifier", "getBoxes"]); + notificationMock = jasmine.createSpyObj("notificationMock", ["httpError"]); let profileResponse = $q.defer(); profileResponse.resolve({ id: 1 }); @@ -28,7 +30,7 @@ describe("Components", () => { }); it("get the profile and store in profile service", done => { - let component: Profile = new Profile(profileServiceMock, $stateParams); + let component: Profile = new Profile(profileServiceMock, $stateParams, notificationMock); $rootScope.$apply(); expect(profileServiceMock.setCurrentProfileByIdentifier).toHaveBeenCalled(); expect(component.profile).toEqual({ id: 1 }); @@ -36,11 +38,26 @@ describe("Components", () => { }); it("get the profile boxes", done => { - let component: Profile = new Profile(profileServiceMock, $stateParams); + let component: Profile = new Profile(profileServiceMock, $stateParams, notificationMock); $rootScope.$apply(); expect(profileServiceMock.getBoxes).toHaveBeenCalled(); expect(component.boxes).toEqual([{ id: 2 }]); done(); }); + + it("display notification error when the profile wasn't found", done => { + let profileResponse = $q.defer(); + profileResponse.reject(); + profileServiceMock.setCurrentProfileByIdentifier = jasmine.createSpy("setCurrentProfileByIdentifier").and.returnValue(profileResponse.promise); + + let component: Profile = new Profile(profileServiceMock, $stateParams, notificationMock); + $rootScope.$apply(); + + expect(profileServiceMock.setCurrentProfileByIdentifier).toHaveBeenCalled(); + expect(notificationMock.httpError).toHaveBeenCalled(); + expect(component.profile).toBeUndefined(); + done(); + }); + }); }); diff --git a/src/app/profile/profile.component.ts b/src/app/profile/profile.component.ts index 3f328ba..4ef53a8 100644 --- a/src/app/profile/profile.component.ts +++ b/src/app/profile/profile.component.ts @@ -6,6 +6,7 @@ import {ContentViewer} from "../content-viewer/content-viewer.component"; import {ContentViewerActions} from "../content-viewer/content-viewer-actions.component"; import {NoosferoActivities} from "../components/noosfero-activities/activities.component"; import {ProfileService} from "../../lib/ng-noosfero-api/http/profile.service"; +import {Notification} from "../components/notification/notification.component"; import * as noosferoModels from "./../models/interfaces"; @@ -13,7 +14,10 @@ import * as noosferoModels from "./../models/interfaces"; selector: 'profile', templateUrl: "app/profile/profile.html", directives: [NoosferoActivities], - providers: [provide('profileService', { useClass: ProfileService })] + providers: [ + provide('profileService', { useClass: ProfileService }), + provide('notification', { useClass: Notification }) + ] }) @StateConfig([ { @@ -75,12 +79,14 @@ export class Profile { boxes: noosferoModels.Box[]; profile: noosferoModels.Profile; - constructor(profileService: ProfileService, $stateParams: ng.ui.IStateParamsService) { + constructor(profileService: ProfileService, $stateParams: ng.ui.IStateParamsService, notification: Notification) { profileService.setCurrentProfileByIdentifier($stateParams["profile"]).then((profile: noosferoModels.Profile) => { this.profile = profile; return profileService.getBoxes(this.profile.id); }).then((response: restangular.IResponse) => { this.boxes = response.data.boxes; + }).catch(() => { + notification.httpError(404, { message: "Profile not found!" }); }); } } diff --git a/src/lib/ng-noosfero-api/http/profile.service.spec.ts b/src/lib/ng-noosfero-api/http/profile.service.spec.ts index 147fb22..e5f1f1e 100644 --- a/src/lib/ng-noosfero-api/http/profile.service.spec.ts +++ b/src/lib/ng-noosfero-api/http/profile.service.spec.ts @@ -24,8 +24,17 @@ describe("Services", () => { it("should return profile by its identifier", (done) => { let identifier = 'profile1'; $httpBackend.expectGET(`/api/v1/profiles?identifier=${identifier}`).respond(200, [{ name: "profile1" }]); - profileService.getByIdentifier(identifier).then((response: restangular.IResponse) => { - expect(response.data[0]).toEqual({ name: "profile1" }); + profileService.getByIdentifier(identifier).then((profile: Profile) => { + expect(profile).toEqual({ name: "profile1" }); + done(); + }); + $httpBackend.flush(); + }); + + it("should reject the promise if the profile wasn't found", (done) => { + let identifier = 'profile1'; + $httpBackend.expectGET(`/api/v1/profiles?identifier=${identifier}`).respond(200, []); + profileService.getByIdentifier(identifier).catch(() => { done(); }); $httpBackend.flush(); diff --git a/src/lib/ng-noosfero-api/http/profile.service.ts b/src/lib/ng-noosfero-api/http/profile.service.ts index 073bbe5..0d32b35 100644 --- a/src/lib/ng-noosfero-api/http/profile.service.ts +++ b/src/lib/ng-noosfero-api/http/profile.service.ts @@ -25,8 +25,8 @@ export class ProfileService { setCurrentProfileByIdentifier(identifier: string) { this.resetCurrentProfile(); - return this.getByIdentifier(identifier).then((response: restangular.IResponse) => { - this.setCurrentProfile(response.data[0]); + return this.getByIdentifier(identifier).then((profile: Profile) => { + this.setCurrentProfile(profile); return this.getCurrentProfile(); }); } @@ -35,8 +35,14 @@ export class ProfileService { return this.get(profileId).customGET("home_page", params); } - getByIdentifier(identifier: string): restangular.IPromise { - return this.restangular.one('profiles').get({ identifier: identifier }); + getByIdentifier(identifier: string): ng.IPromise { + let p = this.restangular.one('profiles').get({ identifier: identifier }); + return p.then((response: restangular.IResponse) => { + if (response.data.length == 0) { + return this.$q.reject(p); + } + return response.data[0]; + }); } getProfileMembers(profileId: number, params?: any): restangular.IPromise { diff --git a/src/spec/mocks.ts b/src/spec/mocks.ts index cbea101..1ff624d 100644 --- a/src/spec/mocks.ts +++ b/src/spec/mocks.ts @@ -60,5 +60,8 @@ export var mocks = { loadScript: (script?: string) => { return Promise.resolve(); } + }, + $log: { + debug: () => { } } }; -- libgit2 0.21.2