Commit 634dc3b59638a5007248fb91db29c2d9051d33a3

Authored by Daniela Feitosa
1 parent 10e51901

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)
app/controllers/my_profile/profile_design_controller.rb
... ... @@ -5,7 +5,7 @@ class ProfileDesignController < BoxOrganizerController
5 5 protect 'edit_profile_design', :profile
6 6  
7 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 10 # blocks exclusive for organizations
11 11 if profile.has_members?
... ...
app/controllers/public/profile_search_controller.rb 0 → 100644
... ... @@ -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 44 if (icon =~ /\//)
45 45 icon
46 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 52 end
49 53 end
50 54  
... ...
app/helpers/forms_helper.rb
... ... @@ -41,8 +41,7 @@ module FormsHelper
41 41 the_class << ' ' << html_options[:class]
42 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 46 bt_submit + bt_cancel
48 47 end
... ...
app/models/article.rb
... ... @@ -473,6 +473,10 @@ class Article &lt; ActiveRecord::Base
473 473 abstract.blank? ? first_paragraph : abstract
474 474 end
475 475  
  476 + def short_lead
  477 + truncate sanitize_html(self.lead), 170, '...'
  478 + end
  479 +
476 480 def creator
477 481 creator_id = versions[0][:last_changed_by_id]
478 482 creator_id && Profile.find(creator_id)
... ... @@ -493,4 +497,9 @@ class Article &lt; ActiveRecord::Base
493 497 tag_name.gsub(/[<>]/, '')
494 498 end
495 499  
  500 + def sanitize_html(text)
  501 + sanitizer = HTML::FullSanitizer.new
  502 + sanitizer.sanitize(text)
  503 + end
  504 +
496 505 end
... ...
app/models/profile_search_block.rb 0 → 100644
... ... @@ -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 &lt; Article
4 4 xss_terminate :only => [ :name, :abstract, :body ], :on => 'validation'
5 5  
6 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 16 end
... ...
app/models/uploaded_file.rb
... ... @@ -47,8 +47,8 @@ class UploadedFile &lt; Article
47 47 delay_attachment_fu_thumbnails
48 48  
49 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 52 else
53 53 'upload-file'
54 54 end
... ...
app/views/blocks/profile_search.rhtml 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +<%= block_title(title) %>
  2 +
  3 +<%= render :partial => 'shared/profile_search_form' %>
... ...
app/views/profile/index.rhtml
... ... @@ -10,6 +10,9 @@
10 10 <%= profile.description %>
11 11 </div>
12 12 <% end %>
  13 + <div id='public-profile-search'>
  14 + <%= render :partial => 'shared/profile_search_form' %>
  15 + </div>
13 16 <% end %>
14 17  
15 18 <table class='profile'>
... ...
app/views/profile_search/_article.rhtml 0 → 100644
... ... @@ -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>
... ...
app/views/profile_search/_folder.rhtml 0 → 100644
... ... @@ -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>
... ...
app/views/profile_search/_uploaded_file.rhtml 0 → 100644
... ... @@ -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 %>
... ...
app/views/profile_search/index.rhtml 0 → 100644
... ... @@ -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>
... ...
app/views/shared/_profile_search_form.rhtml 0 → 100644
... ... @@ -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 75 map.tag 'profile/:profile/tags/:id', :controller => 'profile', :action => 'content_tagged', :id => /.+/, :profile => /#{Noosfero.identifier_format}/
76 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 81 # public profile information
79 82 map.profile 'profile/:profile/:action/:id', :controller => 'profile', :action => 'index', :id => /.*/, :profile => /#{Noosfero.identifier_format}/
80 83  
... ...
features/profile_search.feature 0 → 100644
... ... @@ -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 69 .icon-newupload-file { background-image: url(Tango/16x16/actions/filesave.png) }
70 70 .icon-slideshow { background-image: url(Tango/16x16/mimetypes/x-office-presentation.png) }
71 71 .icon-photos { background-image: url(Tango/16x16/devices/camera-photo.png) }
  72 +
72 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 86 .icon-media-pause { background-image: url(Tango/16x16/actions/media-playback-pause.png) }
75 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 1567 input.button {
1568 1568 background-position: 2px 50%;
1569 1569 }
  1570 +#content form input.button.submit {
  1571 + height: 28px;
  1572 + cursor: pointer;
  1573 +}
1570 1574  
1571 1575 .button span {
1572 1576 display: none;
... ... @@ -4372,6 +4376,98 @@ h1#agenda-title {
4372 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 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 5  
6 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 9 PERSON_BLOCKS = COMMOM_BLOCKS + [FriendsBlock, FavoriteEnterprisesBlock, CommunitiesBlock, EnterprisesBlock ]
10 10 PERSON_BLOCKS_WITH_MEMBERS = PERSON_BLOCKS + [MembersBlock]
11 11 PERSON_BLOCKS_WITH_BLOG = PERSON_BLOCKS + [BlogArchivesBlock]
... ...
test/functional/profile_search_controller_test.rb 0 → 100644
... ... @@ -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&amp;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 &lt; Test::Unit::TestCase
921 921 assert_equal '', a.lead
922 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 934 should 'track action when a published article is created outside a community' do
925 935 article = TinyMceArticle.create! :name => 'Tracked Article', :profile_id => profile.id
926 936 assert article.published?
... ...
test/unit/profile_search_block_test.rb 0 → 100644
... ... @@ -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 &lt; Test::Unit::TestCase
43 43 assert_kind_of Noosfero::TranslatableContent, TextArticle.new
44 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 56 end
... ...