Commit 634dc3b59638a5007248fb91db29c2d9051d33a3
1 parent
10e51901
Exists in
master
and in
29 other branches
Search content of a profile
* new controller profile_search * new block profile_search_block * added on style.css background images for mimetypes icons * new route profile/foo/search?q=bar (ActionItem1734)
Showing
24 changed files
with
468 additions
and
7 deletions
Show diff stats
app/controllers/my_profile/profile_design_controller.rb
@@ -5,7 +5,7 @@ class ProfileDesignController < BoxOrganizerController | @@ -5,7 +5,7 @@ class ProfileDesignController < BoxOrganizerController | ||
5 | protect 'edit_profile_design', :profile | 5 | protect 'edit_profile_design', :profile |
6 | 6 | ||
7 | def available_blocks | 7 | def available_blocks |
8 | - blocks = [ ArticleBlock, TagsBlock, RecentDocumentsBlock, ProfileInfoBlock, LinkListBlock, MyNetworkBlock, FeedReaderBlock, ProfileImageBlock, LocationBlock, SlideshowBlock ] | 8 | + blocks = [ ArticleBlock, TagsBlock, RecentDocumentsBlock, ProfileInfoBlock, LinkListBlock, MyNetworkBlock, FeedReaderBlock, ProfileImageBlock, LocationBlock, SlideshowBlock, ProfileSearchBlock ] |
9 | 9 | ||
10 | # blocks exclusive for organizations | 10 | # blocks exclusive for organizations |
11 | if profile.has_members? | 11 | if profile.has_members? |
@@ -0,0 +1,26 @@ | @@ -0,0 +1,26 @@ | ||
1 | +class ProfileSearchController < PublicController | ||
2 | + | ||
3 | + include SearchHelper | ||
4 | + | ||
5 | + needs_profile | ||
6 | + before_filter :check_access_to_profile | ||
7 | + | ||
8 | + def index | ||
9 | + @q = params[:q].blank? ? '' : params[:q] | ||
10 | + @filtered_query = remove_stop_words(@q) | ||
11 | + if params[:where] == 'environment' | ||
12 | + redirect_to :controller => 'search', :query => @q | ||
13 | + else | ||
14 | + @results = profile.articles.published.find_by_contents(@filtered_query).paginate(:per_page => 10, :page => params[:page]) | ||
15 | + end | ||
16 | + end | ||
17 | + | ||
18 | + protected | ||
19 | + | ||
20 | + def check_access_to_profile | ||
21 | + unless profile.display_info_to?(user) | ||
22 | + redirect_to :controller => 'profile', :action => 'index' | ||
23 | + end | ||
24 | + end | ||
25 | + | ||
26 | +end |
app/helpers/folder_helper.rb
@@ -44,7 +44,11 @@ module FolderHelper | @@ -44,7 +44,11 @@ module FolderHelper | ||
44 | if (icon =~ /\//) | 44 | if (icon =~ /\//) |
45 | icon | 45 | icon |
46 | else | 46 | else |
47 | - 'icon icon-' + icon | 47 | + klasses = 'icon icon-' + icon |
48 | + if article.kind_of?(UploadedFile) | ||
49 | + klasses += ' icon-upload-file' | ||
50 | + end | ||
51 | + klasses | ||
48 | end | 52 | end |
49 | end | 53 | end |
50 | 54 |
app/helpers/forms_helper.rb
@@ -41,8 +41,7 @@ module FormsHelper | @@ -41,8 +41,7 @@ module FormsHelper | ||
41 | the_class << ' ' << html_options[:class] | 41 | the_class << ' ' << html_options[:class] |
42 | end | 42 | end |
43 | 43 | ||
44 | - # FIXME: should be in stylesheet | ||
45 | - bt_submit = submit_tag(label, html_options.merge(:style => 'height:28px; cursor:pointer', :class => the_class)) | 44 | + bt_submit = submit_tag(label, html_options.merge(:class => the_class)) |
46 | 45 | ||
47 | bt_submit + bt_cancel | 46 | bt_submit + bt_cancel |
48 | end | 47 | end |
app/models/article.rb
@@ -473,6 +473,10 @@ class Article < ActiveRecord::Base | @@ -473,6 +473,10 @@ class Article < ActiveRecord::Base | ||
473 | abstract.blank? ? first_paragraph : abstract | 473 | abstract.blank? ? first_paragraph : abstract |
474 | end | 474 | end |
475 | 475 | ||
476 | + def short_lead | ||
477 | + truncate sanitize_html(self.lead), 170, '...' | ||
478 | + end | ||
479 | + | ||
476 | def creator | 480 | def creator |
477 | creator_id = versions[0][:last_changed_by_id] | 481 | creator_id = versions[0][:last_changed_by_id] |
478 | creator_id && Profile.find(creator_id) | 482 | creator_id && Profile.find(creator_id) |
@@ -493,4 +497,9 @@ class Article < ActiveRecord::Base | @@ -493,4 +497,9 @@ class Article < ActiveRecord::Base | ||
493 | tag_name.gsub(/[<>]/, '') | 497 | tag_name.gsub(/[<>]/, '') |
494 | end | 498 | end |
495 | 499 | ||
500 | + def sanitize_html(text) | ||
501 | + sanitizer = HTML::FullSanitizer.new | ||
502 | + sanitizer.sanitize(text) | ||
503 | + end | ||
504 | + | ||
496 | end | 505 | end |
@@ -0,0 +1,22 @@ | @@ -0,0 +1,22 @@ | ||
1 | +class ProfileSearchBlock < Block | ||
2 | + | ||
3 | + def self.description | ||
4 | + _('Display a form to search the profile') | ||
5 | + end | ||
6 | + | ||
7 | + def default_title | ||
8 | + _('Profile search') | ||
9 | + end | ||
10 | + | ||
11 | + def content | ||
12 | + title = self.title | ||
13 | + lambda do | ||
14 | + render :file => 'blocks/profile_search', :locals => { :title => title } | ||
15 | + end | ||
16 | + end | ||
17 | + | ||
18 | + def editable? | ||
19 | + true | ||
20 | + end | ||
21 | + | ||
22 | +end |
app/models/text_article.rb
@@ -4,4 +4,13 @@ class TextArticle < Article | @@ -4,4 +4,13 @@ class TextArticle < Article | ||
4 | xss_terminate :only => [ :name, :abstract, :body ], :on => 'validation' | 4 | xss_terminate :only => [ :name, :abstract, :body ], :on => 'validation' |
5 | 5 | ||
6 | include Noosfero::TranslatableContent | 6 | include Noosfero::TranslatableContent |
7 | + | ||
8 | + def self.icon_name(article = nil) | ||
9 | + if article && !article.parent.nil? && article.parent.kind_of?(Blog) | ||
10 | + Blog.icon_name | ||
11 | + else | ||
12 | + Article.icon_name | ||
13 | + end | ||
14 | + end | ||
15 | + | ||
7 | end | 16 | end |
app/models/uploaded_file.rb
@@ -47,8 +47,8 @@ class UploadedFile < Article | @@ -47,8 +47,8 @@ class UploadedFile < Article | ||
47 | delay_attachment_fu_thumbnails | 47 | delay_attachment_fu_thumbnails |
48 | 48 | ||
49 | def self.icon_name(article = nil) | 49 | def self.icon_name(article = nil) |
50 | - if article && article.image? | ||
51 | - article.public_filename(:icon) | 50 | + if article |
51 | + article.image? ? article.public_filename(:icon) : article.mime_type.gsub(/[\/+.]/, '-') | ||
52 | else | 52 | else |
53 | 'upload-file' | 53 | 'upload-file' |
54 | end | 54 | end |
app/views/profile/index.rhtml
@@ -10,6 +10,9 @@ | @@ -10,6 +10,9 @@ | ||
10 | <%= profile.description %> | 10 | <%= profile.description %> |
11 | </div> | 11 | </div> |
12 | <% end %> | 12 | <% end %> |
13 | + <div id='public-profile-search'> | ||
14 | + <%= render :partial => 'shared/profile_search_form' %> | ||
15 | + </div> | ||
13 | <% end %> | 16 | <% end %> |
14 | 17 | ||
15 | <table class='profile'> | 18 | <table class='profile'> |
@@ -0,0 +1,5 @@ | @@ -0,0 +1,5 @@ | ||
1 | +<li> | ||
2 | + <%= link_to article.title, article.view_url, :class => 'result-title ' + icon_for_article(article) %> | ||
3 | + <p><%= link_to article.short_lead, article.url, {:class => 'article-details'} %></p> | ||
4 | + <div><%= link_to url_for(article.url), article.url, :class => 'article-url' %></div> | ||
5 | +</li> |
@@ -0,0 +1,5 @@ | @@ -0,0 +1,5 @@ | ||
1 | +<li> | ||
2 | + <%= link_to article.title, article.view_url, :class => 'result-title ' + icon_for_article(article) %> | ||
3 | + <p><%= link_to article.body.to_s, article.url, {:class => 'article-details'} %></p> | ||
4 | + <div><%= link_to url_for(article.url), article.url, :class => 'article-url' %></div> | ||
5 | +</li> |
@@ -0,0 +1,11 @@ | @@ -0,0 +1,11 @@ | ||
1 | +<% if article.image? %> | ||
2 | + <li class='result-image'> | ||
3 | + <%= link_to image_tag(article.public_filename(:thumb), :alt => article.display_title), article.view_url, :class => 'article-details' %> | ||
4 | + </li> | ||
5 | +<% else %> | ||
6 | + <li> | ||
7 | + <%= link_to article.title, article.view_url, :class => 'result-title ' + icon_for_article(article) %> | ||
8 | + <p><%= link_to article.title, article.short_lead, :class => 'article-details' %></p> | ||
9 | + <div><%= link_to url_for(article.url), article.url, :class => 'article-url' %></div> | ||
10 | + </li> | ||
11 | +<% end %> |
@@ -0,0 +1,18 @@ | @@ -0,0 +1,18 @@ | ||
1 | +<div id='profile-search-results'> | ||
2 | + <h1><%= _("Search results on %s's profile") % profile.short_name %></h1> | ||
3 | + | ||
4 | + <%= render :partial => 'shared/profile_search_form' %> | ||
5 | + | ||
6 | + <div class='results-found-message'> | ||
7 | + <%= _("%s results found") % @results.total_entries %> | ||
8 | + </div> | ||
9 | + | ||
10 | + <ul class='results-list'> | ||
11 | + <% @results.sort_by { |r| r.is_image? ? 0 : 1}.each do |result| %> | ||
12 | + <%= render :partial => partial_for_class(result.class), :locals => { :article => result } %> | ||
13 | + <% end %> | ||
14 | + </ul> | ||
15 | + | ||
16 | + <%= pagination_links @results %> | ||
17 | + | ||
18 | +</div> |
@@ -0,0 +1,12 @@ | @@ -0,0 +1,12 @@ | ||
1 | +<% form_tag( { :controller => 'profile_search', :profile => profile.identifier}, :method => 'get', :class => 'search_form' ) do %> | ||
2 | + <div class="search-field"> | ||
3 | + <span class="formfield"> | ||
4 | + <%= text_field_tag 'q', @q, :title => _("Find %s's content") % profile.short_name %> | ||
5 | + </span> | ||
6 | + <%= submit_button(:search, _('Search')) %> | ||
7 | + <div> | ||
8 | + <%= labelled_radio_button __('on profile'), 'where', 'profile', true %> | ||
9 | + <%= labelled_radio_button _('on %s') % environment.name, 'where', 'environment', false %> | ||
10 | + </div> | ||
11 | + </div> | ||
12 | +<% end %> |
config/routes.rb
@@ -75,6 +75,9 @@ ActionController::Routing::Routes.draw do |map| | @@ -75,6 +75,9 @@ ActionController::Routing::Routes.draw do |map| | ||
75 | map.tag 'profile/:profile/tags/:id', :controller => 'profile', :action => 'content_tagged', :id => /.+/, :profile => /#{Noosfero.identifier_format}/ | 75 | map.tag 'profile/:profile/tags/:id', :controller => 'profile', :action => 'content_tagged', :id => /.+/, :profile => /#{Noosfero.identifier_format}/ |
76 | map.tag 'profile/:profile/tags', :controller => 'profile', :action => 'tags', :profile => /#{Noosfero.identifier_format}/ | 76 | map.tag 'profile/:profile/tags', :controller => 'profile', :action => 'tags', :profile => /#{Noosfero.identifier_format}/ |
77 | 77 | ||
78 | + # profile search | ||
79 | + map.profile_search 'profile/:profile/search', :controller => 'profile_search', :action => 'index', :profile => /#{Noosfero.identifier_format}/ | ||
80 | + | ||
78 | # public profile information | 81 | # public profile information |
79 | map.profile 'profile/:profile/:action/:id', :controller => 'profile', :action => 'index', :id => /.*/, :profile => /#{Noosfero.identifier_format}/ | 82 | map.profile 'profile/:profile/:action/:id', :controller => 'profile', :action => 'index', :id => /.*/, :profile => /#{Noosfero.identifier_format}/ |
80 | 83 |
@@ -0,0 +1,66 @@ | @@ -0,0 +1,66 @@ | ||
1 | +Feature: search inside a profile | ||
2 | + As a noosfero user | ||
3 | + I want to search | ||
4 | + In order to find stuff from a profile | ||
5 | + | ||
6 | + Background: | ||
7 | + Given the following users | ||
8 | + | login | name | | ||
9 | + | joaosilva | Joao Silva | | ||
10 | + And the following articles | ||
11 | + | owner | name | body | | ||
12 | + | joaosilva | bees and butterflies | this is an article about bees and butterflies | | ||
13 | + | joaosilva | whales and dolphins | this is an article about whales and dolphins | | ||
14 | + | ||
15 | + Scenario: search on profile | ||
16 | + Given I go to Joao Silva's profile | ||
17 | + And I fill in "q" with "bees" | ||
18 | + And I press "Search" | ||
19 | + Then I should see "bees and butterflies" within ".main-block" | ||
20 | + And I should not see "whales and dolphins" within ".main-block" | ||
21 | + | ||
22 | + Scenario: search for event on profile | ||
23 | + Given the following events | ||
24 | + | owner | name | start_date | | ||
25 | + | joaosilva | Group meeting | 2009-10-01 | | ||
26 | + | joaosilva | John Doe's birthday | 2009-09-01 | | ||
27 | + When I go to Joao Silva's profile | ||
28 | + And I fill in "q" with "birthday" | ||
29 | + And I press "Search" | ||
30 | + Then I should see "John Doe's birthday" within ".main-block" | ||
31 | + And I should not see "Group meeting" within ".main-block" | ||
32 | + | ||
33 | + Scenario: simple search for event on profile search block | ||
34 | + Given the following blocks | ||
35 | + | owner | type | | ||
36 | + | joaosilva | ProfileSearchBlock | | ||
37 | + When I go to Joao Silva's profile | ||
38 | + And I fill in "q" with "bees" within ".profile-search-block" | ||
39 | + And I press "Search" | ||
40 | + Then I should see "bees and butterflies" within ".main-block" | ||
41 | + | ||
42 | + Scenario: not display unpublished articles | ||
43 | + Given the following articles | ||
44 | + | owner | name | body | published | | ||
45 | + | joaosilva | published article | this is a public article | true | | ||
46 | + | joaosilva | unpublished article | this is a private article | false | | ||
47 | + And I go to Joao Silva's profile | ||
48 | + And I fill in "q" with "article" | ||
49 | + And I press "Search" | ||
50 | + Then I should see "public article" within ".main-block" | ||
51 | + And I should not see "private article" within ".main-block" | ||
52 | + | ||
53 | + Scenario: search on environment | ||
54 | + Given I go to Joao Silva's profile | ||
55 | + And I fill in "q" with "bees" | ||
56 | + And I choose "on Colivre.net" | ||
57 | + And I press "Search" | ||
58 | + Then I should be on the search page | ||
59 | + And I should see "bees and butterflies" within "#search-page" | ||
60 | + | ||
61 | + Scenario: not display search on private profiles | ||
62 | + Given the following users | ||
63 | + | login | name | public_profile | | ||
64 | + | mariasilva | Maria Silva | false | | ||
65 | + And I go to /profile/mariasilva/search | ||
66 | + Then I should see "friends only" |
public/designs/icons/tango/style.css
@@ -69,7 +69,19 @@ | @@ -69,7 +69,19 @@ | ||
69 | .icon-newupload-file { background-image: url(Tango/16x16/actions/filesave.png) } | 69 | .icon-newupload-file { background-image: url(Tango/16x16/actions/filesave.png) } |
70 | .icon-slideshow { background-image: url(Tango/16x16/mimetypes/x-office-presentation.png) } | 70 | .icon-slideshow { background-image: url(Tango/16x16/mimetypes/x-office-presentation.png) } |
71 | .icon-photos { background-image: url(Tango/16x16/devices/camera-photo.png) } | 71 | .icon-photos { background-image: url(Tango/16x16/devices/camera-photo.png) } |
72 | + | ||
72 | .icon-text-html { background-image: url(Tango/16x16/mimetypes/text-html.png) } | 73 | .icon-text-html { background-image: url(Tango/16x16/mimetypes/text-html.png) } |
74 | +.icon-text-plain { background-image: url(Tango/16x16/mimetypes/text-x-generic.png) } | ||
75 | +.icon-image-svg-xml { background-image: url(Tango/16x16/mimetypes/image-x-generic.png) } | ||
76 | +.icon-application-octet-stream { background-image: url(Tango/16x16/mimetypes/binary.png) } | ||
77 | +.icon-application-x-gzip { background-image: url(Tango/16x16/mimetypes/gnome-mime-application-x-gzip.png) } | ||
78 | +.icon-application-postscript { background-image: url(Tango/16x16/mimetypes/gnome-mime-application-postscript.png) } | ||
79 | +.icon-application-pdf { background-image: url(Tango/16x16/mimetypes/gnome-mime-application-pdf.png) } | ||
80 | +.icon-application-ogg { background-image: url(Tango/16x16/mimetypes/gnome-mime-application-ogg.png) } | ||
81 | +.icon-video-mpeg { background-image: url(Tango/16x16/mimetypes/video-x-generic.png) } | ||
82 | +.icon-application-vnd-oasis-opendocument-text { background-image: url(Tango/16x16/mimetypes/gnome-mime-application-vnd.oasis.opendocument.text.png) } | ||
83 | +.icon-application-vnd-oasis-opendocument-spreadsheet { background-image: url(Tango/16x16/mimetypes/gnome-mime-application-vnd.oasis.opendocument.spreadsheet.png) } | ||
84 | +.icon-application-vnd-oasis-opendocument-presentation { background-image: url(Tango/16x16/mimetypes/gnome-mime-application-vnd.oasis.opendocument.presentation.png) } | ||
73 | 85 | ||
74 | .icon-media-pause { background-image: url(Tango/16x16/actions/media-playback-pause.png) } | 86 | .icon-media-pause { background-image: url(Tango/16x16/actions/media-playback-pause.png) } |
75 | .icon-media-play { background-image: url(Tango/16x16/actions/media-playback-start.png) } | 87 | .icon-media-play { background-image: url(Tango/16x16/actions/media-playback-start.png) } |
public/stylesheets/application.css
@@ -1567,6 +1567,10 @@ input.button { | @@ -1567,6 +1567,10 @@ input.button { | ||
1567 | input.button { | 1567 | input.button { |
1568 | background-position: 2px 50%; | 1568 | background-position: 2px 50%; |
1569 | } | 1569 | } |
1570 | +#content form input.button.submit { | ||
1571 | + height: 28px; | ||
1572 | + cursor: pointer; | ||
1573 | +} | ||
1570 | 1574 | ||
1571 | .button span { | 1575 | .button span { |
1572 | display: none; | 1576 | display: none; |
@@ -4372,6 +4376,98 @@ h1#agenda-title { | @@ -4372,6 +4376,98 @@ h1#agenda-title { | ||
4372 | text-align: left; | 4376 | text-align: left; |
4373 | } | 4377 | } |
4374 | 4378 | ||
4379 | +/* * * Profile search * * * * * * * */ | ||
4380 | + | ||
4381 | +#public-profile-search, | ||
4382 | +#profile-search-results form, | ||
4383 | +.profile-search-block form { | ||
4384 | + padding: 10px; | ||
4385 | + margin-bottom: 15px; | ||
4386 | + background-color: #E6E6E6; | ||
4387 | + -moz-border-radius: 5px; | ||
4388 | + -webkit-border-radius: 5px; | ||
4389 | +} | ||
4390 | + | ||
4391 | +#public-profile-search .formfield input { | ||
4392 | + width: 400px; | ||
4393 | +} | ||
4394 | + | ||
4395 | +/* * * Profile Search Results * * * * * * * */ | ||
4396 | + | ||
4397 | +#profile-search-results ul { | ||
4398 | + padding-left: 0px; | ||
4399 | +} | ||
4400 | + | ||
4401 | +#profile-search-results form .formfield input { | ||
4402 | + width: 395px; | ||
4403 | +} | ||
4404 | + | ||
4405 | +#profile-search-results form .search_form .button { | ||
4406 | + margin-top: 5px; | ||
4407 | +} | ||
4408 | + | ||
4409 | +#profile-search-results li { | ||
4410 | + list-style: none; | ||
4411 | + margin-bottom: 20px; | ||
4412 | + clear: left; | ||
4413 | +} | ||
4414 | + | ||
4415 | +#profile-search-results li.result-image { | ||
4416 | + float: left; | ||
4417 | + clear: none; | ||
4418 | + height: 150px; | ||
4419 | + margin-right: 10px; | ||
4420 | + margin-left: 10px; | ||
4421 | +} | ||
4422 | + | ||
4423 | +#profile-search-results .result-title { | ||
4424 | + font-size: 18px; | ||
4425 | +} | ||
4426 | + | ||
4427 | +#profile-search-results p { | ||
4428 | + margin-top: 5px; | ||
4429 | + margin-bottom: 0px; | ||
4430 | +} | ||
4431 | + | ||
4432 | +#profile-search-results .article-details { | ||
4433 | + color: #000; | ||
4434 | + text-decoration: none; | ||
4435 | +} | ||
4436 | + | ||
4437 | +#profile-search-results a.article-url { | ||
4438 | + text-decoration: none; | ||
4439 | + color: #77AA44; | ||
4440 | +} | ||
4441 | + | ||
4442 | +#profile-search-results a.article-url:hover { | ||
4443 | + text-decoration: underline; | ||
4444 | +} | ||
4445 | + | ||
4446 | +#profile-search-results .results-list .icon { | ||
4447 | + background-repeat: no-repeat; | ||
4448 | + background-position: left; | ||
4449 | + padding: 0px 0px 3px 20px; | ||
4450 | + border: none; | ||
4451 | +} | ||
4452 | + | ||
4453 | +#profile-search-results .results-list .icon:hover { | ||
4454 | + background-color: transparent; | ||
4455 | +} | ||
4456 | + | ||
4457 | +#profile-search-results .results-found-message { | ||
4458 | + margin-top: 10px; | ||
4459 | + font-style: italic; | ||
4460 | +} | ||
4461 | + | ||
4462 | +/* * * Profile search block * * * * * * * */ | ||
4463 | + | ||
4464 | +.profile-search-block .formfield input { | ||
4465 | + width: 100%; | ||
4466 | +} | ||
4467 | + | ||
4468 | +.profile-search-block .button.icon-search { | ||
4469 | + display: none; | ||
4470 | +} | ||
4375 | 4471 | ||
4376 | /* * * Sub-category stuff * * * * * * * */ | 4472 | /* * * Sub-category stuff * * * * * * * */ |
4377 | 4473 |
test/functional/profile_design_controller_test.rb
@@ -5,7 +5,7 @@ class ProfileDesignController; def rescue_action(e) raise e end; end | @@ -5,7 +5,7 @@ class ProfileDesignController; def rescue_action(e) raise e end; end | ||
5 | 5 | ||
6 | class ProfileDesignControllerTest < Test::Unit::TestCase | 6 | class ProfileDesignControllerTest < Test::Unit::TestCase |
7 | 7 | ||
8 | - COMMOM_BLOCKS = [ ArticleBlock, TagsBlock, RecentDocumentsBlock, ProfileInfoBlock, LinkListBlock, MyNetworkBlock, FeedReaderBlock, ProfileImageBlock, LocationBlock, SlideshowBlock] | 8 | + COMMOM_BLOCKS = [ ArticleBlock, TagsBlock, RecentDocumentsBlock, ProfileInfoBlock, LinkListBlock, MyNetworkBlock, FeedReaderBlock, ProfileImageBlock, LocationBlock, SlideshowBlock, ProfileSearchBlock ] |
9 | PERSON_BLOCKS = COMMOM_BLOCKS + [FriendsBlock, FavoriteEnterprisesBlock, CommunitiesBlock, EnterprisesBlock ] | 9 | PERSON_BLOCKS = COMMOM_BLOCKS + [FriendsBlock, FavoriteEnterprisesBlock, CommunitiesBlock, EnterprisesBlock ] |
10 | PERSON_BLOCKS_WITH_MEMBERS = PERSON_BLOCKS + [MembersBlock] | 10 | PERSON_BLOCKS_WITH_MEMBERS = PERSON_BLOCKS + [MembersBlock] |
11 | PERSON_BLOCKS_WITH_BLOG = PERSON_BLOCKS + [BlogArchivesBlock] | 11 | PERSON_BLOCKS_WITH_BLOG = PERSON_BLOCKS + [BlogArchivesBlock] |
@@ -0,0 +1,106 @@ | @@ -0,0 +1,106 @@ | ||
1 | +require File.dirname(__FILE__) + '/../test_helper' | ||
2 | +require 'profile_search_controller' | ||
3 | + | ||
4 | +# Re-raise errors caught by the controller. | ||
5 | +class ProfileSearchController; def rescue_action(e) raise e end; end | ||
6 | + | ||
7 | +class ProfileSearchControllerTest < Test::Unit::TestCase | ||
8 | + def setup | ||
9 | + @controller = ProfileSearchController.new | ||
10 | + @request = ActionController::TestRequest.new | ||
11 | + @response = ActionController::TestResponse.new | ||
12 | + | ||
13 | + @person = fast_create(Person) | ||
14 | + end | ||
15 | + attr_reader :person | ||
16 | + | ||
17 | + should 'filter stop words' do | ||
18 | + @controller.expects(:locale).returns('en').at_least_once | ||
19 | + get 'index', :profile => person.identifier, :q => 'an article about something' | ||
20 | + assert_response :success | ||
21 | + assert_template 'index' | ||
22 | + assert_equal 'article something', assigns('filtered_query') | ||
23 | + end | ||
24 | + | ||
25 | + should 'espape xss attack' do | ||
26 | + @controller.expects(:profile).returns(person).at_least_once | ||
27 | + get 'index', :profile => person.identifier, :q => '<wslite>' | ||
28 | + assert_no_tag :tag => 'wslite' | ||
29 | + end | ||
30 | + | ||
31 | + should 'render success in search' do | ||
32 | + get :index, :profile => person.identifier, :q => 'something not important' | ||
33 | + assert_response :success | ||
34 | + end | ||
35 | + | ||
36 | + should 'search for articles' do | ||
37 | + article = TextileArticle.create(:name => 'My article', :body => 'Article to test profile search', :profile => person) | ||
38 | + | ||
39 | + get 'index', :profile => person.identifier, :q => 'article profile' | ||
40 | + assert_includes assigns(:results), article | ||
41 | + end | ||
42 | + | ||
43 | + should 'display search results' do | ||
44 | + article1 = fast_create(Article, :body => '<p>Article to test profile search</p>', :profile_id => person.id) | ||
45 | + article2 = fast_create(Article, :body => '<p>Another article to test profile search</p>', :profile_id => person.id) | ||
46 | + | ||
47 | + get 'index', :profile => person.identifier, :q => 'article' | ||
48 | + | ||
49 | + [article1, article2].each do |article| | ||
50 | + assert_tag :tag => 'li', :descendant => { :tag => 'a', :content => article.short_lead, :attributes => { :class => /article-details/ }} | ||
51 | + end | ||
52 | + end | ||
53 | + | ||
54 | + should 'paginate results listing' do | ||
55 | + (1..11).each do |i| | ||
56 | + TextileArticle.create!(:name => "Article #{i}", :profile => person, :language => 'en') | ||
57 | + end | ||
58 | + | ||
59 | + get 'index', :profile => person.identifier, :q => 'Article' | ||
60 | + | ||
61 | + assert_equal 10, assigns(:results).size | ||
62 | + assert_tag :tag => 'a', :attributes => { :href => "/profile/#{person.identifier}/search?page=2&q=Article", :rel => 'next' } | ||
63 | + end | ||
64 | + | ||
65 | + should 'display abstract if given' do | ||
66 | + article1 = TextileArticle.create(:name => 'Article 1', :abstract => 'Abstract to test', :body => 'Article to test profile search', :profile => person) | ||
67 | + article2 = TextileArticle.create(:name => 'Article 2', :body => 'Another article to test profile search', :profile => person) | ||
68 | + | ||
69 | + get 'index', :profile => person.identifier, :q => 'article profile' | ||
70 | + | ||
71 | + assert_tag :tag => 'li', :descendant => { :tag => 'a', :content => article1.abstract, :attributes => { :class => /article-details/ }} | ||
72 | + assert_no_tag :tag => 'li', :descendant => { :tag => 'a', :content => article1.body, :attributes => { :class => /article-details/ }} | ||
73 | + | ||
74 | + assert_tag :tag => 'li', :descendant => { :tag => 'a', :content => article2.body, :attributes => { :class => /article-details/ }} | ||
75 | + end | ||
76 | + | ||
77 | + should 'display nothing if search is blank' do | ||
78 | + article1 = TextileArticle.create(:name => 'Article 1', :body => 'Article to test profile search', :profile => person) | ||
79 | + article2 = TextileArticle.create(:name => 'Article 2', :body => 'Another article to test profile search', :profile => person) | ||
80 | + | ||
81 | + get 'index', :profile => person.identifier, :q => '' | ||
82 | + | ||
83 | + assert_no_tag :tag => 'ul', :attributes => { :id => 'profile-search-results'}, :descendant => { :tag => 'li' } | ||
84 | + end | ||
85 | + | ||
86 | + should 'not display private articles' do | ||
87 | + article1 = TextileArticle.create(:name => 'Article 1', :body => 'Article to test profile search', :profile => person, :published => false) | ||
88 | + article2 = TextileArticle.create(:name => 'Article 2', :body => 'Another article to test profile search', :profile => person) | ||
89 | + | ||
90 | + get 'index', :profile => person.identifier, :q => 'article profile' | ||
91 | + | ||
92 | + assert_no_tag :tag => 'li', :descendant => { :tag => 'a', :content => article1.body, :attributes => { :class => /article-details/ }} | ||
93 | + | ||
94 | + assert_tag :tag => 'li', :descendant => { :tag => 'a', :content => article2.body, :attributes => { :class => /article-details/ }} | ||
95 | + end | ||
96 | + | ||
97 | + should 'display number of results found' do | ||
98 | + article1 = TextileArticle.create(:name => 'Article 1', :body => 'Article to test profile search', :body => 'Article to test profile search', :profile => person) | ||
99 | + article2 = TextileArticle.create(:name => 'Article 2', :body => 'Another article to test profile search', :profile => person) | ||
100 | + | ||
101 | + get 'index', :profile => person.identifier, :q => 'article profile' | ||
102 | + | ||
103 | + assert_tag :tag => 'div', :attributes => { :class => 'results-found-message' }, :content => /2 results found/ | ||
104 | + end | ||
105 | + | ||
106 | +end |
test/unit/article_test.rb
@@ -921,6 +921,16 @@ class ArticleTest < Test::Unit::TestCase | @@ -921,6 +921,16 @@ class ArticleTest < Test::Unit::TestCase | ||
921 | assert_equal '', a.lead | 921 | assert_equal '', a.lead |
922 | end | 922 | end |
923 | 923 | ||
924 | + should 'have short lead' do | ||
925 | + a = fast_create(TinyMceArticle, :body => '<p>' + ('a' *180) + '</p>') | ||
926 | + assert_equal 170, a.short_lead.length | ||
927 | + end | ||
928 | + | ||
929 | + should 'remove html from short lead' do | ||
930 | + a = Article.new(:body => "<p>an article with html that should be <em>removed</em></p>") | ||
931 | + assert_equal 'an article with html that should be removed', a.short_lead | ||
932 | + end | ||
933 | + | ||
924 | should 'track action when a published article is created outside a community' do | 934 | should 'track action when a published article is created outside a community' do |
925 | article = TinyMceArticle.create! :name => 'Tracked Article', :profile_id => profile.id | 935 | article = TinyMceArticle.create! :name => 'Tracked Article', :profile_id => profile.id |
926 | assert article.published? | 936 | assert article.published? |
@@ -0,0 +1,32 @@ | @@ -0,0 +1,32 @@ | ||
1 | +require File.dirname(__FILE__) + '/../test_helper' | ||
2 | + | ||
3 | +class ProfileSearchBlockTest < Test::Unit::TestCase | ||
4 | + | ||
5 | + should 'describe itself' do | ||
6 | + assert_not_equal Block.description, ProfileSearchBlock.description | ||
7 | + end | ||
8 | + | ||
9 | + should 'provide a default title' do | ||
10 | + assert_not_equal Block.new.default_title, ProfileSearchBlock.new.default_title | ||
11 | + end | ||
12 | + | ||
13 | + should 'render profile search' do | ||
14 | + person = fast_create(Person) | ||
15 | + | ||
16 | + block = ProfileSearchBlock.new | ||
17 | + block.stubs(:owner).returns(person) | ||
18 | + | ||
19 | + self.expects(:render).with(:file => 'blocks/profile_search', :locals => { :title => block.title}) | ||
20 | + instance_eval(& block.content) | ||
21 | + end | ||
22 | + | ||
23 | + should 'provide view_title' do | ||
24 | + person = fast_create(Person) | ||
25 | + person.boxes << Box.new | ||
26 | + block = ProfileSearchBlock.new(:title => 'Title from block') | ||
27 | + person.boxes.first.blocks << block | ||
28 | + block.save! | ||
29 | + assert_equal 'Title from block', block.view_title | ||
30 | + end | ||
31 | + | ||
32 | +end |
test/unit/text_article_test.rb
@@ -43,4 +43,14 @@ class TextArticleTest < Test::Unit::TestCase | @@ -43,4 +43,14 @@ class TextArticleTest < Test::Unit::TestCase | ||
43 | assert_kind_of Noosfero::TranslatableContent, TextArticle.new | 43 | assert_kind_of Noosfero::TranslatableContent, TextArticle.new |
44 | end | 44 | end |
45 | 45 | ||
46 | + should 'return article icon name' do | ||
47 | + assert_equal Article.icon_name, TextArticle.icon_name | ||
48 | + end | ||
49 | + | ||
50 | + should 'return blog icon name if the article is a blog post' do | ||
51 | + blog = fast_create(Blog) | ||
52 | + article = TextArticle.new(:parent => blog) | ||
53 | + assert_equal Blog.icon_name, TextArticle.icon_name(article) | ||
54 | + end | ||
55 | + | ||
46 | end | 56 | end |