Commit 634dc3b59638a5007248fb91db29c2d9051d33a3
1 parent
10e51901
Exists in
master
and in
23 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 |