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 | 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? | ... | ... |
| ... | ... | @@ -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
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 < 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 < 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 | ... | ... |
| ... | ... | @@ -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 | 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 < 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/profile/index.rhtml
| ... | ... | @@ -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 @@ |
| 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 @@ |
| 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 @@ |
| 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 @@ |
| 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 | ... | ... |
| ... | ... | @@ -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] | ... | ... |
| ... | ... | @@ -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 | 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? | ... | ... |
| ... | ... | @@ -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 | 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 | ... | ... |