Commit d0fa7823b63317f1649830850aeb477bcabf7b18

Authored by Ábner Oliveira
2 parents 04a99db3 575daccf

Merge branch 'search' into 'master'

Component to search for articles in the environment

Issue #49

See merge request !37
src/app/environment/environment.component.ts
@@ -2,6 +2,7 @@ import {StateConfig, Component, Inject, provide} from 'ng-forward'; @@ -2,6 +2,7 @@ import {StateConfig, Component, Inject, provide} from 'ng-forward';
2 import {EnvironmentService} from "../../lib/ng-noosfero-api/http/environment.service"; 2 import {EnvironmentService} from "../../lib/ng-noosfero-api/http/environment.service";
3 import {NotificationService} from "../shared/services/notification.service"; 3 import {NotificationService} from "../shared/services/notification.service";
4 import {EnvironmentHomeComponent} from "./environment-home.component"; 4 import {EnvironmentHomeComponent} from "./environment-home.component";
  5 +import {SearchComponent} from "../search/search.component";
5 6
6 /** 7 /**
7 * @ngdoc controller 8 * @ngdoc controller
@@ -29,6 +30,18 @@ import {EnvironmentHomeComponent} from "./environment-home.component"; @@ -29,6 +30,18 @@ import {EnvironmentHomeComponent} from "./environment-home.component";
29 controllerAs: "vm" 30 controllerAs: "vm"
30 } 31 }
31 } 32 }
  33 + },
  34 + {
  35 + url: '^/search?query',
  36 + component: SearchComponent,
  37 + name: 'main.environment.search',
  38 + views: {
  39 + "mainBlockContent": {
  40 + templateUrl: "app/search/search.html",
  41 + controller: SearchComponent,
  42 + controllerAs: "ctrl"
  43 + }
  44 + }
32 } 45 }
33 ]) 46 ])
34 @Inject(EnvironmentService, "$state", "currentEnvironment") 47 @Inject(EnvironmentService, "$state", "currentEnvironment")
src/app/layout/navbar/navbar.html
@@ -43,6 +43,9 @@ @@ -43,6 +43,9 @@
43 <language-selector class="nav navbar-nav navbar-right"></language-selector> 43 <language-selector class="nav navbar-nav navbar-right"></language-selector>
44 </ul> 44 </ul>
45 <div ui-view="actions"></div> 45 <div ui-view="actions"></div>
  46 + <div class="nav navbar-nav search navbar-right">
  47 + <search-form></search-form>
  48 + </div>
46 </div> 49 </div>
47 </div> 50 </div>
48 </nav> 51 </nav>
src/app/layout/navbar/navbar.scss
@@ -34,6 +34,16 @@ @@ -34,6 +34,16 @@
34 text-align: center; 34 text-align: center;
35 } 35 }
36 36
  37 + .search {
  38 + .search-input {
  39 + height: 45px;
  40 + margin-top: 10px;
  41 + }
  42 + .input-group-btn {
  43 + padding-top: 10px;
  44 + }
  45 + }
  46 +
37 @media (max-width: $break-sm-max) { 47 @media (max-width: $break-sm-max) {
38 .navbar-toggle .fa-bars{ 48 .navbar-toggle .fa-bars{
39 font-size: 14pt; 49 font-size: 14pt;
src/app/layout/scss/skins/_whbl.scss
@@ -180,27 +180,19 @@ $whbl-font-color: #16191c; @@ -180,27 +180,19 @@ $whbl-font-color: #16191c;
180 } 180 }
181 .pagination { 181 .pagination {
182 > li { 182 > li {
183 - > a,  
184 - > span,  
185 - > a:hover,  
186 - > span:hover,  
187 - > a:focus,  
188 - > span:focus,  
189 - > a:active,  
190 - > span:active {  
191 - color: $whbl-primary-color; 183 + &.active {
  184 + a {
  185 + background-color: $whbl-primary-color;
  186 + color: #FFF;
  187 + }
192 } 188 }
193 - }  
194 - > .active {  
195 - > a,  
196 - > span,  
197 - > a:hover,  
198 - > span:hover,  
199 - > a:focus,  
200 - > span:focus {  
201 - background-color: $whbl-primary-color;  
202 - border-color: $whbl-primary-color;  
203 - color: #fff; 189 + > a {
  190 + &:hover {
  191 + background-color: $whbl-primary-color;
  192 + color: #FFF;
  193 + }
  194 + background-color: #FFF;
  195 + color: $whbl-primary-color;
204 } 196 }
205 } 197 }
206 } 198 }
src/app/main/main.component.ts
@@ -40,6 +40,8 @@ import {SidebarComponent} from &quot;../layout/sidebar/sidebar.component&quot;; @@ -40,6 +40,8 @@ import {SidebarComponent} from &quot;../layout/sidebar/sidebar.component&quot;;
40 import {MainBlockComponent} from "../layout/blocks/main/main-block.component"; 40 import {MainBlockComponent} from "../layout/blocks/main/main-block.component";
41 import {HtmlEditorComponent} from "../shared/components/html-editor/html-editor.component"; 41 import {HtmlEditorComponent} from "../shared/components/html-editor/html-editor.component";
42 import {PermissionDirective} from "../shared/components/permission/permission.directive"; 42 import {PermissionDirective} from "../shared/components/permission/permission.directive";
  43 +import {SearchComponent} from "../search/search.component";
  44 +import {SearchFormComponent} from "../search/search-form/search-form.component";
43 45
44 /** 46 /**
45 * @ngdoc controller 47 * @ngdoc controller
@@ -100,7 +102,7 @@ export class EnvironmentContent { @@ -100,7 +102,7 @@ export class EnvironmentContent {
100 LinkListBlockComponent, CommunitiesBlockComponent, HtmlEditorComponent, ProfileComponent, 102 LinkListBlockComponent, CommunitiesBlockComponent, HtmlEditorComponent, ProfileComponent,
101 MainBlockComponent, RecentDocumentsBlockComponent, Navbar, SidebarComponent, ProfileImageBlockComponent, 103 MainBlockComponent, RecentDocumentsBlockComponent, Navbar, SidebarComponent, ProfileImageBlockComponent,
102 MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent, StatisticsBlockComponent, 104 MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent, StatisticsBlockComponent,
103 - LoginBlockComponent, CustomContentComponent, PermissionDirective 105 + LoginBlockComponent, CustomContentComponent, PermissionDirective, SearchFormComponent, SearchComponent
104 ].concat(plugins.mainComponents).concat(plugins.hotspots), 106 ].concat(plugins.mainComponents).concat(plugins.hotspots),
105 providers: [AuthService, SessionService, NotificationService, BodyStateClassesService, 107 providers: [AuthService, SessionService, NotificationService, BodyStateClassesService,
106 "ngAnimate", "ngCookies", "ngStorage", "ngTouch", 108 "ngAnimate", "ngCookies", "ngStorage", "ngTouch",
src/app/search/search-form/search-form.component.spec.ts 0 → 100644
@@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
  1 +import {ComponentTestHelper, createClass} from "../../../spec/component-test-helper";
  2 +import {SearchFormComponent} from "./search-form.component";
  3 +import * as helpers from "../../../spec/helpers";
  4 +
  5 +const htmlTemplate: string = '<search-form></search-form>';
  6 +
  7 +describe("Components", () => {
  8 + describe("Search Form Component", () => {
  9 +
  10 + let helper: ComponentTestHelper<SearchFormComponent>;
  11 + let stateMock = jasmine.createSpyObj("$state", ["go"]);
  12 +
  13 + beforeEach(angular.mock.module("templates"));
  14 +
  15 + beforeEach((done) => {
  16 + let cls = createClass({
  17 + template: htmlTemplate,
  18 + directives: [SearchFormComponent],
  19 + providers: [helpers.createProviderToValue("$state", stateMock)]
  20 + });
  21 + helper = new ComponentTestHelper<SearchFormComponent>(cls, done);
  22 + });
  23 +
  24 + it("render a input for search query", () => {
  25 + expect(helper.find(".search-input").length).toEqual(1);
  26 + });
  27 +
  28 + it("go to search page when click on search button", () => {
  29 + helper.component.query = 'query';
  30 + helper.component.search();
  31 + expect(stateMock.go).toHaveBeenCalledWith('main.environment.search', { query: 'query' });
  32 + });
  33 + });
  34 +});
src/app/search/search-form/search-form.component.ts 0 → 100644
@@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
  1 +import {Component, Inject} from "ng-forward";
  2 +
  3 +@Component({
  4 + selector: 'search-form',
  5 + templateUrl: 'app/search/search-form/search-form.html'
  6 +})
  7 +@Inject("$state")
  8 +export class SearchFormComponent {
  9 +
  10 + query: string;
  11 +
  12 + constructor(private $state: ng.ui.IStateService) {
  13 + }
  14 +
  15 + ngOnInit() {
  16 + this.query = this.$state.params['query'];
  17 + }
  18 +
  19 + search() {
  20 + this.$state.go('main.environment.search', { query: this.query });
  21 + }
  22 +
  23 + isSearchPage() {
  24 + return "main.environment.search" === this.$state.current.name;
  25 + }
  26 +}
src/app/search/search-form/search-form.html 0 → 100644
@@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
  1 +<form class="navbar-form search-form" role="search" ng-if="!ctrl.isSearchPage()">
  2 + <div class="input-group">
  3 + <input type="text" class="search-input form-control" placeholder="Search" name="q" ng-model="ctrl.query">
  4 + <div class="input-group-btn">
  5 + <button class="btn btn-default" type="submit" (click)="ctrl.search()"><i class="fa fa-search fa-fw"></i></button>
  6 + </div>
  7 + </div>
  8 +</form>
src/app/search/search.component.spec.ts 0 → 100644
@@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
  1 +import {ComponentTestHelper, createClass} from "../../spec/component-test-helper";
  2 +import {SearchComponent} from "./search.component";
  3 +import * as helpers from "../../spec/helpers";
  4 +
  5 +const htmlTemplate: string = '<search></search>';
  6 +
  7 +describe("Components", () => {
  8 + describe("Search Component", () => {
  9 +
  10 + let helper: ComponentTestHelper<SearchComponent>;
  11 + let stateParams = { query: 'query' };
  12 + let articleService = jasmine.createSpyObj("ArticleService", ["search"]);
  13 + let result = Promise.resolve({ data: [{ id: 1 }], headers: (param: string) => { return 1; } });
  14 + articleService.search = jasmine.createSpy("search").and.returnValue(result);
  15 +
  16 + beforeEach(angular.mock.module("templates"));
  17 +
  18 + beforeEach((done) => {
  19 + let cls = createClass({
  20 + template: htmlTemplate,
  21 + directives: [SearchComponent],
  22 + providers: [
  23 + helpers.createProviderToValue("$stateParams", stateParams),
  24 + helpers.createProviderToValue("ArticleService", articleService)
  25 + ].concat(helpers.provideFilters("truncateFilter", "stripTagsFilter"))
  26 + });
  27 + helper = new ComponentTestHelper<SearchComponent>(cls, done);
  28 + });
  29 +
  30 + it("load first page with search results", () => {
  31 + expect(articleService.search).toHaveBeenCalledWith({ query: 'query', per_page: 10, page: 0 });
  32 + });
  33 +
  34 + it("display search results", () => {
  35 + expect(helper.all(".result").length).toEqual(1);
  36 + });
  37 + });
  38 +});
src/app/search/search.component.ts 0 → 100644
@@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
  1 +import {Component, Inject} from "ng-forward";
  2 +import {ArticleService} from "./../../lib/ng-noosfero-api/http/article.service";
  3 +
  4 +import {SearchFormComponent} from "./search-form/search-form.component";
  5 +
  6 +@Component({
  7 + selector: 'search',
  8 + templateUrl: 'app/search/search.html',
  9 + directives: [SearchFormComponent]
  10 +})
  11 +@Inject(ArticleService, "$stateParams", "$state")
  12 +export class SearchComponent {
  13 +
  14 + articles: noosfero.Article[];
  15 + query: string;
  16 + totalResults = 0;
  17 + perPage = 10;
  18 + currentPage: number = 0;
  19 +
  20 + constructor(private articleService: ArticleService, private $stateParams: ng.ui.IStateParamsService, private $state: ng.ui.IStateService) {
  21 + this.query = this.$stateParams['query'];
  22 + this.loadPage();
  23 + }
  24 +
  25 + search() {
  26 + this.$state.go('main.environment.search', { query: this.query });
  27 + }
  28 +
  29 + loadPage() {
  30 + let filters = {
  31 + query: this.query,
  32 + per_page: this.perPage,
  33 + page: this.currentPage
  34 + };
  35 + this.articleService.search(filters).then((result: noosfero.RestResult<noosfero.Article[]>) => {
  36 + this.totalResults = <number>result.headers("total");
  37 + this.articles = result.data;
  38 + });
  39 + }
  40 +}
src/app/search/search.html 0 → 100644
@@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
  1 +<form ng-submit="ctrl.search()">
  2 +<label for="query" ng-bind-html="'search.results.query.label' | translate"></label>
  3 +<input id="query" placeholder="{{'search.results.query.placeholder' | translate}}" type="search" class="search-box-title" ng-model="ctrl.query">
  4 +</form>
  5 +<div class="search-results">
  6 + <div class="summary">
  7 + {{"search.results.summary" | translate:{results: ctrl.totalResults}:"messageformat"}}
  8 + </div>
  9 + <div ng-repeat="article in ctrl.articles | orderBy: 'created_at':true" class="result">
  10 + <a class="title" ui-sref="main.profile.page({profile: article.profile.identifier, page: article.path})">
  11 + <h4 ng-bind="article.title"></h4>
  12 + </a>
  13 + <div class="info">
  14 + <a class="profile" ui-sref="main.profile.home({profile: article.profile.identifier})">
  15 + {{article.profile.name}}
  16 + </a>
  17 + <span class="bullet-separator">•</span>
  18 + <span class="time">
  19 + <span am-time-ago="article.created_at | dateFormat"></span>
  20 + </span>
  21 + </div>
  22 + <div class="post-lead" ng-bind-html="article.body | stripTags | truncate: 250: '...': true"></div>
  23 + </div>
  24 + <uib-pagination ng-model="ctrl.currentPage" total-items="ctrl.totalResults" class="pagination-sm center-block"
  25 + boundary-links="true" items-per-page="ctrl.perPage" ng-change="ctrl.loadPage()"
  26 + first-text="«" last-text="»" previous-text="‹" next-text="›">
  27 + </uib-pagination>
  28 +</div>
src/app/search/search.scss 0 → 100644
@@ -0,0 +1,48 @@ @@ -0,0 +1,48 @@
  1 +.search-results {
  2 + .summary {
  3 + color: #bbbbbb;
  4 + font-size: 13px;
  5 + border-top: 1px solid #ececec;
  6 + }
  7 + .result {
  8 + margin: 25px 0;
  9 +
  10 + .title {
  11 + h4 {
  12 + margin: 0;
  13 + }
  14 + }
  15 + .info {
  16 + .profile {
  17 + color: #6e9e7b;
  18 + }
  19 + .time {
  20 + color: #6e9e7b;
  21 + font-size: 12px;
  22 + }
  23 + .bullet-separator {
  24 + margin: 0 2px;
  25 + font-size: 10px;
  26 + color: #afd6ba;
  27 + }
  28 + }
  29 + }
  30 +}
  31 +
  32 +.search-box-title {
  33 + border: 0;
  34 + border-bottom: 1px solid rgba(0,0,0,.15);
  35 + border-radius: 0;
  36 + font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Open Sans","Helvetica Neue",sans-serif;
  37 + letter-spacing: 0;
  38 + font-weight: 300;
  39 + font-style: normal;
  40 + font-size: 50px;
  41 + height: 80px;
  42 + padding: 0;
  43 + width: 100%;
  44 +}
  45 +
  46 +.search-box-title:focus {
  47 + outline: none;
  48 +}
0 \ No newline at end of file 49 \ No newline at end of file
src/languages/en.json
@@ -74,5 +74,8 @@ @@ -74,5 +74,8 @@
74 "profile.content.success.message": "Profile saved!", 74 "profile.content.success.message": "Profile saved!",
75 "custom_content.title": "Edit content", 75 "custom_content.title": "Edit content",
76 "profile.custom_header.label": "Header", 76 "profile.custom_header.label": "Header",
77 - "profile.custom_footer.label": "Footer" 77 + "profile.custom_footer.label": "Footer",
  78 + "search.results.summary": "{results, plural, one{result} other{# results}}",
  79 + "search.results.query.label": "Search for:",
  80 + "search.results.query.placeholder": "Search"
78 } 81 }
src/languages/pt.json
@@ -58,7 +58,7 @@ @@ -58,7 +58,7 @@
58 "article.remove.success.title": "Bom trabalho!", 58 "article.remove.success.title": "Bom trabalho!",
59 "article.remove.success.message": "Artigo removido!", 59 "article.remove.success.message": "Artigo removido!",
60 "article.remove.confirmation.title": "Tem certeza?", 60 "article.remove.confirmation.title": "Tem certeza?",
61 - "article.remove.confirmation.message": "Não será possível recuperar este artigo!", 61 + "article.remove.confirmation.message": "Não será possível recuperar este artigo!",
62 "article.basic_editor.visibility": "Visibilidade", 62 "article.basic_editor.visibility": "Visibilidade",
63 "article.basic_editor.visibility.public": "Público", 63 "article.basic_editor.visibility.public": "Público",
64 "article.basic_editor.visibility.private": "Privado", 64 "article.basic_editor.visibility.private": "Privado",
@@ -74,5 +74,8 @@ @@ -74,5 +74,8 @@
74 "profile.content.success.message": "Perfil salvo!", 74 "profile.content.success.message": "Perfil salvo!",
75 "custom_content.title": "Editar conteúdo", 75 "custom_content.title": "Editar conteúdo",
76 "profile.custom_header.label": "Cabeçalho", 76 "profile.custom_header.label": "Cabeçalho",
77 - "profile.custom_footer.label": "Rodapé" 77 + "profile.custom_footer.label": "Rodapé",
  78 + "search.results.summary": "{results, plural, one{# resultado} other{# resultados}}",
  79 + "search.results.query.label": "Buscar:",
  80 + "search.results.query.placeholder": "Informe aqui sua busca"
78 } 81 }
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.remove(<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();
@@ -32,7 +32,7 @@ describe(&quot;Services&quot;, () =&gt; { @@ -32,7 +32,7 @@ describe(&quot;Services&quot;, () =&gt; {
32 it("should return article children", (done) => { 32 it("should return article children", (done) => {
33 let articleId = 1; 33 let articleId = 1;
34 $httpBackend.expectGET(`/api/v1/articles/${articleId}/children`).respond(200, { articles: [{ name: "article1" }] }); 34 $httpBackend.expectGET(`/api/v1/articles/${articleId}/children`).respond(200, { articles: [{ name: "article1" }] });
35 - articleService.getChildren(<noosfero.Article>{id: articleId}).then((result: noosfero.RestResult<noosfero.Article[]>) => { 35 + articleService.getChildren(<noosfero.Article>{ id: articleId }).then((result: noosfero.RestResult<noosfero.Article[]>) => {
36 expect(result.data).toEqual([{ name: "article1" }]); 36 expect(result.data).toEqual([{ name: "article1" }]);
37 done(); 37 done();
38 }); 38 });
@@ -42,7 +42,7 @@ describe(&quot;Services&quot;, () =&gt; { @@ -42,7 +42,7 @@ describe(&quot;Services&quot;, () =&gt; {
42 it("should get articles by profile", (done) => { 42 it("should get articles by profile", (done) => {
43 let profileId = 1; 43 let profileId = 1;
44 $httpBackend.expectGET(`/api/v1/profiles/${profileId}/articles`).respond(200, { articles: [{ name: "article1" }] }); 44 $httpBackend.expectGET(`/api/v1/profiles/${profileId}/articles`).respond(200, { articles: [{ name: "article1" }] });
45 - articleService.getByProfile(<noosfero.Profile>{id: profileId}).then((result: noosfero.RestResult<noosfero.Article[]>) => { 45 + articleService.getByProfile(<noosfero.Profile>{ id: profileId }).then((result: noosfero.RestResult<noosfero.Article[]>) => {
46 expect(result.data).toEqual([{ name: "article1" }]); 46 expect(result.data).toEqual([{ name: "article1" }]);
47 done(); 47 done();
48 }); 48 });
@@ -52,7 +52,7 @@ describe(&quot;Services&quot;, () =&gt; { @@ -52,7 +52,7 @@ describe(&quot;Services&quot;, () =&gt; {
52 it("should get articles by profile with additional filters", (done) => { 52 it("should get articles by profile with additional filters", (done) => {
53 let profileId = 1; 53 let profileId = 1;
54 $httpBackend.expectGET(`/api/v1/profiles/${profileId}/articles?path=test`).respond(200, { articles: [{ name: "article1" }] }); 54 $httpBackend.expectGET(`/api/v1/profiles/${profileId}/articles?path=test`).respond(200, { articles: [{ name: "article1" }] });
55 - articleService.getByProfile(<noosfero.Profile>{id: profileId}, { path: 'test' }).then((result: noosfero.RestResult<noosfero.Article[]>) => { 55 + articleService.getByProfile(<noosfero.Profile>{ id: profileId }, { path: 'test' }).then((result: noosfero.RestResult<noosfero.Article[]>) => {
56 expect(result.data).toEqual([{ name: "article1" }]); 56 expect(result.data).toEqual([{ name: "article1" }]);
57 done(); 57 done();
58 }); 58 });
@@ -62,7 +62,7 @@ describe(&quot;Services&quot;, () =&gt; { @@ -62,7 +62,7 @@ describe(&quot;Services&quot;, () =&gt; {
62 it("should get article children with additional filters", (done) => { 62 it("should get article children with additional filters", (done) => {
63 let articleId = 1; 63 let articleId = 1;
64 $httpBackend.expectGET(`/api/v1/articles/${articleId}/children?path=test`).respond(200, { articles: [{ name: "article1" }] }); 64 $httpBackend.expectGET(`/api/v1/articles/${articleId}/children?path=test`).respond(200, { articles: [{ name: "article1" }] });
65 - articleService.getChildren(<noosfero.Article>{id: articleId}, { path: 'test' }).then((result: noosfero.RestResult<noosfero.Article[]>) => { 65 + articleService.getChildren(<noosfero.Article>{ id: articleId }, { path: 'test' }).then((result: noosfero.RestResult<noosfero.Article[]>) => {
66 expect(result.data).toEqual([{ name: "article1" }]); 66 expect(result.data).toEqual([{ name: "article1" }]);
67 done(); 67 done();
68 }); 68 });
@@ -71,14 +71,25 @@ describe(&quot;Services&quot;, () =&gt; { @@ -71,14 +71,25 @@ describe(&quot;Services&quot;, () =&gt; {
71 71
72 it("should create an article in a profile", (done) => { 72 it("should create an article in a profile", (done) => {
73 let profileId = 1; 73 let profileId = 1;
74 - let article: noosfero.Article = <any>{ id: null};  
75 - $httpBackend.expectPOST(`/api/v1/profiles/${profileId}/articles`, { article: article }).respond(200, {article: { id: 2 }});  
76 - articleService.createInProfile(<noosfero.Profile>{id: profileId}, article).then((result: noosfero.RestResult<noosfero.Article>) => { 74 + let article: noosfero.Article = <any>{ id: null };
  75 + $httpBackend.expectPOST(`/api/v1/profiles/${profileId}/articles`, { article: article }).respond(200, { article: { id: 2 } });
  76 + articleService.createInProfile(<noosfero.Profile>{ id: profileId }, article).then((result: noosfero.RestResult<noosfero.Article>) => {
77 expect(result.data).toEqual({ id: 2 }); 77 expect(result.data).toEqual({ id: 2 });
78 done(); 78 done();
79 }); 79 });
80 $httpBackend.flush(); 80 $httpBackend.flush();
81 }); 81 });
  82 +
  83 + it("should search for articles in environment", (done) => {
  84 + let profileId = 1;
  85 + let article: noosfero.Article = <any>{ id: null };
  86 + $httpBackend.expectGET(`/api/v1/search/article?query=query`).respond(200, { articles: [{ id: 2 }] });
  87 + articleService.search({ query: 'query' }).then((result: noosfero.RestResult<noosfero.Article[]>) => {
  88 + expect(result.data).toEqual([{ id: 2 }]);
  89 + done();
  90 + });
  91 + $httpBackend.flush();
  92 + });
82 }); 93 });
83 94
84 95
src/lib/ng-noosfero-api/http/article.service.ts
@@ -118,6 +118,11 @@ export class ArticleService extends RestangularService&lt;noosfero.Article&gt; { @@ -118,6 +118,11 @@ export class ArticleService extends RestangularService&lt;noosfero.Article&gt; {
118 return this.listSubElements(<noosfero.Article>articleElement, "children", params); 118 return this.listSubElements(<noosfero.Article>articleElement, "children", params);
119 } 119 }
120 120
  121 + search(params: any): ng.IPromise<noosfero.RestResult<noosfero.Article[]>> {
  122 + let deferred = this.$q.defer<noosfero.RestResult<noosfero.Article[]>>();
  123 + let restRequest = this.restangularService.all("search").customGET('article', params);
  124 + restRequest.then(this.getHandleSuccessFunction(deferred)).catch(this.getHandleErrorFunction(deferred));
  125 + return deferred.promise;
  126 + }
121 127
122 } 128 }
123 -