diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index bca6838e..00000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "doc/manual"] - path = doc/manual - url = git://github.com/bsag/tracks_manual.git diff --git a/.yardopts b/.yardopts index 88651e3e..924f2990 100644 --- a/.yardopts +++ b/.yardopts @@ -1,3 +1,5 @@ --title "Tracks Documentation" --charset utf-8 --markup="textile" +--private +--protected diff --git a/app/controllers/login_controller.rb b/app/controllers/login_controller.rb index 52716b6f..fed1dc3a 100644 --- a/app/controllers/login_controller.rb +++ b/app/controllers/login_controller.rb @@ -69,16 +69,7 @@ class LoginController < ApplicationController end def logout - @user.forget_me if logged_in? - cookies.delete :auth_token - session['user_id'] = nil - if ( SITE_CONFIG['authentication_schemes'].include? 'cas') && session[:cas_user] - CASClient::Frameworks::Rails::Filter.logout(self) - else - reset_session - notify :notice, t('login.logged_out') - redirect_to_login - end + logout_user end def expire_session @@ -149,13 +140,6 @@ class LoginController < ApplicationController private - def redirect_to_login - respond_to do |format| - format.html { redirect_to login_path } - format.m { redirect_to login_path(:format => 'm') } - end - end - def should_expire_sessions? session['noexpiry'] != "on" end diff --git a/app/controllers/preferences_controller.rb b/app/controllers/preferences_controller.rb index e1c8638c..0f0d8df9 100644 --- a/app/controllers/preferences_controller.rb +++ b/app/controllers/preferences_controller.rb @@ -12,8 +12,11 @@ class PreferencesController < ApplicationController user_updated = current_user.update_attributes(params['user']) prefs_updated = current_user.preference.update_attributes(params['prefs']) if (user_updated && prefs_updated) - notify :notice, "Preferences updated" - redirect_to :action => 'index' + if !params['user']['password'].blank? # password updated? + logout_user t('preferences.password_changed') + else + preference_updated + end else msg = "Preferences could not be updated: " msg += "User model errors; " unless user_updated @@ -28,4 +31,12 @@ class PreferencesController < ApplicationController render :text => l(Date.today, :format => format) end +private + + # Display notification if preferences are successful updated + def preference_updated + notify :notice, t('preferences.updated') + redirect_to :action => 'index' + end + end diff --git a/app/controllers/todos_controller.rb b/app/controllers/todos_controller.rb index b626653f..61aee981 100644 --- a/app/controllers/todos_controller.rb +++ b/app/controllers/todos_controller.rb @@ -2,8 +2,8 @@ class TodosController < ApplicationController helper :todos - skip_before_filter :login_required, :only => [:index, :calendar] - prepend_before_filter :login_or_feed_token_required, :only => [:index, :calendar] + skip_before_filter :login_required, :only => [:index, :calendar, :tag] + prepend_before_filter :login_or_feed_token_required, :only => [:index, :calendar, :tag] append_before_filter :find_and_activate_ready, :only => [:index, :list_deferred] # TODO: replace :except with :only @@ -586,34 +586,34 @@ class TodosController < ApplicationController # /todos/tag/[tag_name] shows all the actions tagged with tag_name def tag - init_data_for_sidebar unless mobile? + get_params_for_tag_view + @page_title = t('todos.tagged_page_title', :tag_name => @tag_title) @source_view = params['_source_view'] || 'tag' - @tag_name = sanitize(params[:name]) # sanitize to prevent XSS vunerability! - @page_title = t('todos.tagged_page_title', :tag_name => @tag_name) - # mobile tags are routed with :name ending on .m. So we need to chomp it - @tag_name = @tag_name.chomp('.m') if mobile? + if mobile? + # mobile tags are routed with :name ending on .m. So we need to chomp it + @tag_name = @tag_name.chomp('.m') + else + init_data_for_sidebar + end - @tag = Tag.find_by_name(@tag_name) - @tag = Tag.new(:name => @tag_name) if @tag.nil? + todos_with_tag_ids = find_todos_with_tag_expr(@tag_expr) - @not_done_todos = current_user.todos.with_tag(@tag).active.not_hidden.find(:all, + @not_done_todos = todos_with_tag_ids.active.not_hidden.find(:all, :order => 'todos.due IS NULL, todos.due ASC, todos.created_at ASC', :include => Todo::DEFAULT_INCLUDES) - @hidden_todos = current_user.todos.with_tag(@tag).hidden.find(:all, + @hidden_todos = todos_with_tag_ids.hidden.find(:all, :include => Todo::DEFAULT_INCLUDES, :order => 'todos.completed_at DESC, todos.created_at DESC') - @deferred = current_user.todos.with_tag(@tag).deferred.find(:all, - :order => 'show_from ASC, todos.created_at DESC', :include => Todo::DEFAULT_INCLUDES) - @pending = current_user.todos.with_tag(@tag).blocked.find(:all, - :order => 'show_from ASC, todos.created_at DESC', :include => Todo::DEFAULT_INCLUDES) + @deferred = todos_with_tag_ids.deferred.find(:all, + :order => 'todos.show_from ASC, todos.created_at DESC', :include => Todo::DEFAULT_INCLUDES) + @pending = todos_with_tag_ids.blocked.find(:all, + :order => 'todos.show_from ASC, todos.created_at DESC', :include => Todo::DEFAULT_INCLUDES) # If you've set no_completed to zero, the completed items box isn't shown on # the tag page - max_completed = current_user.prefs.show_number_completed - @done = current_user.todos.with_tag(@tag).completed.find(:all, - :limit => max_completed, - :order => 'todos.completed_at DESC', - :include => Todo::DEFAULT_INCLUDES) + @done = todos_with_tag_ids.completed.find(:all, + :limit => current_user.prefs.show_number_completed, + :order => 'todos.completed_at DESC', :include => Todo::DEFAULT_INCLUDES) @projects = current_user.projects @contexts = current_user.contexts @@ -635,6 +635,9 @@ class TodosController < ApplicationController cookies[:mobile_url]= {:value => request.request_uri, :secure => SITE_CONFIG['secure_cookies']} render :action => "mobile_tag" } + format.text { + render :action => 'index', :layout => false, :content_type => Mime::TEXT + } end end @@ -645,7 +648,7 @@ class TodosController < ApplicationController @tag = Tag.find_by_name(@tag_name) @tag = Tag.new(:name => @tag_name) if @tag.nil? - completed_todos = current_user.todos.completed.with_tag(@tag) + completed_todos = current_user.todos.completed.with_tag(@tag.id) @done_today = get_done_today(completed_todos) @done_this_week = get_done_this_week(completed_todos) @@ -662,7 +665,7 @@ class TodosController < ApplicationController @tag = Tag.find_by_name(@tag_name) @tag = Tag.new(:name => @tag_name) if @tag.nil? - @done = current_user.todos.completed.with_tag(@tag).paginate :page => params[:page], :per_page => 20, :order => 'completed_at DESC', :include => Todo::DEFAULT_INCLUDES + @done = current_user.todos.completed.with_tag(@tag.id).paginate :page => params[:page], :per_page => 20, :order => 'completed_at DESC', :include => Todo::DEFAULT_INCLUDES @count = @done.size render :template => 'todos/all_done' end @@ -988,6 +991,58 @@ class TodosController < ApplicationController :include => [ :project, :context, :tags ]) end + def tag_title(tag_expr) + and_list = tag_expr.inject([]) { |s,tag_list| s << tag_list.join(',') } + return and_list.join(' AND ') + end + + def get_params_for_tag_view + # use sanitize to prevent XSS attacks + + @tag_expr = [] + @tag_expr << sanitize(params[:name]).split(',') + @tag_expr << sanitize(params[:and]).split(',') if params[:and] + + i = 1 + while params['and'+i.to_s] + @tag_expr << sanitize(params['and'+i.to_s]).split(',') + i=i+1 + end + + @single_tag = @tag_expr.size == 1 && @tag_expr[0].size == 1 + @tag_name = @tag_expr[0][0] + @tag_title = @single_tag ? @tag_name : tag_title(@tag_expr) + end + + def get_ids_from_tag_expr(tag_expr) + ids = [] + tag_expr.each do |tag_list| + id_list = [] + tag_list.each do |tag| + tag = Tag.find_by_name(tag) + id_list << tag.id if tag + end + ids << id_list + end + return ids + end + + def find_todos_with_tag_expr(tag_expr) + # optimize for the common case: selecting only one tag + if @single_tag + tag = Tag.find_by_name(@tag_name) + tag_id = tag.nil? ? -1 : tag.id + return current_user.todos.with_tag(tag_id) + end + + tag_ids = get_ids_from_tag_expr(tag_expr) + todos = current_user.todos + tag_ids.each do |ids| + todos = todos.with_tags(ids) unless ids.nil? || ids.empty? + end + return todos + end + def determine_down_count source_view do |from| from.todo do @@ -1019,7 +1074,7 @@ class TodosController < ApplicationController if @tag.nil? @tag = Tag.new(:name => @tag_name) end - @down_count = current_user.todos.with_tag(@tag).active.not_hidden.count + @down_count = current_user.todos.with_tag(@tag.id).active.not_hidden.count end end end @@ -1036,10 +1091,10 @@ class TodosController < ApplicationController if tag.nil? tag = Tag.new(:name => params['tag']) end - @remaining_deferred_or_pending_count = current_user.todos.with_tag(tag).deferred_or_blocked.count - @remaining_in_context = current_user.contexts.find(context_id).todos.active.not_hidden.with_tag(tag).count - @target_context_count = current_user.contexts.find(@todo.context_id).todos.active.not_hidden.with_tag(tag).count - @remaining_hidden_count = current_user.todos.hidden.with_tag(tag).count + @remaining_deferred_or_pending_count = current_user.todos.with_tag(tag.id).deferred_or_blocked.count + @remaining_in_context = current_user.contexts.find(context_id).todos.active.not_hidden.with_tag(tag.id).count + @target_context_count = current_user.contexts.find(@todo.context_id).todos.active.not_hidden.with_tag(tag.id).count + @remaining_hidden_count = current_user.todos.hidden.with_tag(tag.id).count } from.project { project_id = @project_changed ? @original_item_project_id : @todo.project_id @@ -1090,7 +1145,7 @@ class TodosController < ApplicationController end end from.tag do - @completed_count = current_user.todos.with_tag(@tag).completed.count + @completed_count = current_user.todos.with_tag(@tag.id).completed.count end end end @@ -1098,7 +1153,7 @@ class TodosController < ApplicationController def determine_deferred_tag_count(tag_name) tag = Tag.find_by_name(tag_name) # tag.nil? should normally not happen, but is a workaround for #929 - @remaining_deferred_or_pending_count = tag.nil? ? 0 : current_user.todos.deferred.with_tag(tag).count + @remaining_deferred_or_pending_count = tag.nil? ? 0 : current_user.todos.deferred.with_tag(tag.id).count end def render_todos_html diff --git a/app/models/todo.rb b/app/models/todo.rb index ea6e17c9..06b23699 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -28,17 +28,18 @@ class Todo < ActiveRecord::Base named_scope :deferred_or_blocked, :conditions => ["(todos.completed_at IS NULL AND NOT(todos.show_from IS NULL)) OR (todos.state = ?)", "pending"] named_scope :not_deferred_or_blocked, :conditions => ["todos.completed_at IS NULL AND todos.show_from IS NULL AND NOT(todos.state = ?)", "pending"] named_scope :hidden, - :joins => :context, - :conditions => ["todos.state = ? OR (contexts.hide = ? AND (todos.state = ? OR todos.state = ? OR todos.state = ?))", + :joins => "INNER JOIN contexts c_hidden ON c_hidden.id = todos.context_id", + :conditions => ["todos.state = ? OR (c_hidden.hide = ? AND (todos.state = ? OR todos.state = ? OR todos.state = ?))", 'project_hidden', true, 'active', 'deferred', 'pending'] named_scope :not_hidden, - :joins => [:context], - :conditions => ['NOT(todos.state = ? OR (contexts.hide = ? AND (todos.state = ? OR todos.state = ? OR todos.state = ?)))', + :joins => "INNER JOIN contexts c_hidden ON c_hidden.id = todos.context_id", + :conditions => ['NOT(todos.state = ? OR (c_hidden.hide = ? AND (todos.state = ? OR todos.state = ? OR todos.state = ?)))', 'project_hidden', true, 'active', 'deferred', 'pending'] # other scopes named_scope :are_due, :conditions => ['NOT (todos.due IS NULL)'] - named_scope :with_tag, lambda { |tag| {:joins => :taggings, :conditions => ["taggings.tag_id = ? ", tag.id] } } + named_scope :with_tag, lambda { |tag_id| {:joins => :taggings, :conditions => ["taggings.tag_id = ? ", tag_id] } } + named_scope :with_tags, lambda { |tag_ids| {:conditions => ["EXISTS(SELECT * from taggings t WHERE t.tag_id IN (?) AND t.taggable_id=todos.id AND t.taggable_type='Todo')", tag_ids] } } named_scope :of_user, lambda { |user_id| {:conditions => ["todos.user_id = ? ", user_id] } } named_scope :completed_after, lambda { |date| {:conditions => ["todos.completed_at > ? ", date] } } named_scope :completed_before, lambda { |date| {:conditions => ["todos.completed_at < ? ", date] } } diff --git a/app/views/layouts/standard.html.erb b/app/views/layouts/standard.html.erb index f47cfa96..611ccdc7 100644 --- a/app/views/layouts/standard.html.erb +++ b/app/views/layouts/standard.html.erb @@ -38,7 +38,10 @@ +
+ <%= NOTIFY_BAR %> +

<% if @count -%> diff --git a/app/views/projects/update.js.erb b/app/views/projects/update.js.erb index 45b7df79..270a38b0 100644 --- a/app/views/projects/update.js.erb +++ b/app/views/projects/update.js.erb @@ -30,7 +30,7 @@ function update_project_page() { remove_project_edit_form(); update_and_show_project_settings(); TracksForm.set_project_name("<%= escape_javascript(@project.name)%>"); - $("h2#project_name").html("<%= escape_javascript(@project.name)%>"); + $("h2 div#project_name").html("<%= escape_javascript(@project.name)%>"); <% if @project.default_context %> TracksForm.set_context_name_and_default_context_name("<%= escape_javascript(@project.default_context.name)%>"); <% end %> diff --git a/app/views/todos/mobile_tag.rhtml b/app/views/todos/mobile_tag.rhtml index 5dd8efd3..4dde5250 100644 --- a/app/views/todos/mobile_tag.rhtml +++ b/app/views/todos/mobile_tag.rhtml @@ -2,24 +2,24 @@ <% if @not_done_todos.empty? -%>

<%= t('todos.no_actions_found_title') %>

-
<%= t('todos.no_actions_with', :tag_name => @tag_name) %>
+
<%= t('todos.no_actions_with', :tag_name => @tag_title) %>
<% end -%> <%= render :partial => "contexts/mobile_context", :collection => @contexts_to_show -%> -

<%= t('todos.deferred_actions_with', :tag_name=> @tag_name) %>

+

<%= t('todos.deferred_actions_with', :tag_name=> @tag_title) %>

<% unless (@deferred.nil? or @deferred.size == 0) -%> <%= render :partial => "todos/mobile_todo", :collection => @deferred, :locals => { :parent_container_type => "tag" } -%> -
+ <% else -%> -<%= t('todos.no_deferred_actions_with', :tag_name => @tag_name) %> +<%= t('todos.no_deferred_actions_with', :tag_name => @tag_title) %> <% end -%> -

<%= t('todos.completed_actions_with', :tag_name => @tag_name) %>

+

<%= t('todos.completed_actions_with', :tag_name => @tag_title) %>

<% unless (@done.nil? or @done.size == 0) -%> <%= render :partial => "todos/mobile_todo", :collection => @done, :locals => { :parent_container_type => "tag" } %>
<% else -%> -<%= t('todos.no_completed_actions_with', :tag_name => @tag_name) %> +<%= t('todos.no_completed_actions_with', :tag_name => @tag_title) %> <% end -%>
\ No newline at end of file diff --git a/app/views/todos/tag.html.erb b/app/views/todos/tag.html.erb index 646f153c..64506aa1 100644 --- a/app/views/todos/tag.html.erb +++ b/app/views/todos/tag.html.erb @@ -8,22 +8,22 @@ :locals => { :collapsible => true } %> <% unless @deferred.nil? -%> - <%= render :partial => "todos/deferred", :locals => { - :deferred => @deferred, + <%= render :partial => "todos/deferred", :locals => { + :deferred => @deferred, :pending => @pending, - :collapsible => true, - :append_descriptor => t('todos.tagged_with', :tag_name => @tag_name), - :parent_container_type => 'tag' + :collapsible => true, + :append_descriptor => t('todos.tagged_with', :tag_name => @tag_title), + :parent_container_type => 'tag' } %> <% end -%> <% unless @hidden_todos.nil? -%> - <%= render :partial => "todos/hidden", :object => @hidden_todos, :locals => { :collapsible => true, :append_descriptor => t('todos.tagged_with', :tag_name => @tag_name) } %> + <%= render :partial => "todos/hidden", :object => @hidden_todos, :locals => { :collapsible => true, :append_descriptor => t('todos.tagged_with', :tag_name => @tag_title) } %> <% end -%> <% unless @done.nil? -%> <%= render :partial => "todos/completed", :object => @done, - :locals => { :collapsible => true, :append_descriptor => t('todos.tagged_with', :tag_name => @tag_name) } %> + :locals => { :collapsible => true, :append_descriptor => t('todos.tagged_with', :tag_name => @tag_title) } %> <% end -%>
diff --git a/config/environment.rb b/config/environment.rb index 1fc8f03b..61db438c 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -107,6 +107,9 @@ if ( SITE_CONFIG['authentication_schemes'].include? 'cas') end end +# changed in development.rb to show under_construction bar +NOTIFY_BAR = "" unless defined?(NOTIFY_BAR) + tracks_version='2.1devel' # comment out next two lines if you do not want (or can not) the date of the # last git commit in the footer diff --git a/config/environments/development.rb b/config/environments/development.rb index c30f67cf..05c880d7 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -14,4 +14,6 @@ config.action_controller.perform_caching = false config.action_mailer.raise_delivery_errors = false # Unique cookies -config.action_controller.session = { :key => 'TracksDev' } \ No newline at end of file +config.action_controller.session = { :key => 'TracksDev' } + +NOTIFY_BAR="
 
" diff --git a/config/initializers/mongrel_workaround.rb b/config/initializers/mongrel_workaround.rb index dcad6931..60b207b9 100644 --- a/config/initializers/mongrel_workaround.rb +++ b/config/initializers/mongrel_workaround.rb @@ -4,7 +4,7 @@ def check_mongrel_around_115 begin # Gem.available? is deprecated from rubygems 1.8.2 Gem::Specification::find_by_name "mongrel", "~>1.1.5" - rescue Gem::LoadError + rescue if RUBY_VERSION[2] == "9" false else diff --git a/config/locales/en.yml b/config/locales/en.yml index 3c908e1d..9feff005 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -391,10 +391,10 @@ en: all_completed: All completed actions feed_title_in_context: in context '%{context}' older_than_days: "" - completed_tagged_page_title: TRACKS::Completed tasks with tag %{tag_name} + completed_tagged_page_title: TRACKS::Completed tasks with tag '%{tag_name}' edit: Edit pending: Pending - completed_actions_with: Completed actions with the tag %{tag_name} + completed_actions_with: Completed actions with the tag '%{tag_name}' deleted_success: The action was deleted succesfully. completed_tasks_title: TRACKS::Completed tasks feed_title_in_project: in project '%{project}' @@ -659,6 +659,8 @@ en: staleness_starts_after: Staleness starts after %{days} days change_identity_url: Change Your Identity URL change_password: Change your password + password_changed: You password has been changed, please log on again. + updated: Preferences updated page_title: TRACKS::Preferences title: Your preferences token_description: Token (for feeds and API use) diff --git a/config/routes.rb b/config/routes.rb index 60f777cd..ec0ed738 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -40,6 +40,7 @@ ActionController::Routing::Routes.draw do |map| # UPDATE: added support for mobile view. All tags ending on .m will be # routed to mobile view of tags. todos.mobile_tag 'todos/tag/:name.m', :action => "tag", :format => 'm' + todos.text_tag 'todos/tag/:name.txt', :action => "tag", :format => 'txt' todos.tag 'todos/tag/:name', :action => "tag", :name => /.*/ todos.done_tag 'todos/done/tag/:name', :action => "done_tag" todos.all_done_tag 'todos/all_done/tag/:name', :action => "all_done_tag" diff --git a/doc/manual b/doc/manual deleted file mode 160000 index 8cb38474..00000000 --- a/doc/manual +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8cb3847448223c5f0397a98e92a93a6af656e69d diff --git a/features/shared_add_new_todo.feature b/features/shared_add_new_todo.feature index 11f1cc65..8e48d567 100644 --- a/features/shared_add_new_todo.feature +++ b/features/shared_add_new_todo.feature @@ -95,7 +95,7 @@ Feature: Add new next action from every page | tickler page | not see | | "test project" project | see | | context page for "test context" | see | - | tag page for "starred" | not see | + | tag page for "starred" | see | @selenium Scenario Outline: I can add multiple todos from several pages diff --git a/features/step_definitions/project_steps.rb b/features/step_definitions/project_steps.rb index cfb3589d..145cbd06 100644 --- a/features/step_definitions/project_steps.rb +++ b/features/step_definitions/project_steps.rb @@ -274,7 +274,7 @@ end Then /^the project title should be "(.*)"$/ do |title| wait_for :timeout => 2 do - selenium.get_text("css=h2#project_name") == title + selenium.get_text("css=h2#project_name_container div#project_name") == title end end diff --git a/lib/login_system.rb b/lib/login_system.rb index 8bec423a..8a8a70a1 100644 --- a/lib/login_system.rb +++ b/lib/login_system.rb @@ -9,6 +9,22 @@ module LoginSystem def prefs current_user.prefs unless current_user.nil? end + + # Logout the {#current_user} and redirect to login page + # + # @param [String] message notification to display + def logout_user message=t('login.logged_out') + @user.forget_me if logged_in? + cookies.delete :auth_token + session['user_id'] = nil + if ( SITE_CONFIG['authentication_schemes'].include? 'cas') && session[:cas_user] + CASClient::Frameworks::Rails::Filter.logout(self) + else + reset_session + notify :notice, message + redirect_to_login + end + end protected @@ -132,7 +148,7 @@ module LoginSystem def set_current_user(user) @user = user end - + # overwrite if you want to have special behavior in case the user is not authorized # to access the current operation. # the default action is to redirect to the login screen @@ -192,4 +208,14 @@ module LoginSystem render :text => t('login.unsuccessful'), :status => 401 end +private + + # Redirect the user to the login page. + def redirect_to_login + respond_to do |format| + format.html { redirect_to login_path } + format.m { redirect_to login_path(:format => 'm') } + end + end + end \ No newline at end of file diff --git a/public/500.html b/public/500.html index ec3bbf02..4bc0a825 100644 --- a/public/500.html +++ b/public/500.html @@ -24,7 +24,7 @@

We're sorry, but something went wrong.

-

We've been notified about this issue and we'll take a look at it shortly.

+

The Tracks application failed with error 500. More details can be found in the log file of Tracks

diff --git a/public/images/construction.gif b/public/images/construction.gif new file mode 100644 index 00000000..95ef4e62 Binary files /dev/null and b/public/images/construction.gif differ diff --git a/public/javascripts/application.js b/public/javascripts/application.js index 280bf6bf..7e5ec824 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -305,19 +305,19 @@ var TracksPages = { $(".todo_notes").toggle(); }); - + /* Poor man's perspectives, allows to hide any context that is collapsed */ $("#toggle-contexts-nav").click(function () { /* Need to keep a single toggle across all contexts */ - $(this).toggleClass("context_visibility"); + $(this).toggleClass("context_visibility"); if ($(this).hasClass("context_visibility")) { $(".context_collapsed").hide(); /* Hide all collapsed contexts together*/ } else { - $(".context_collapsed").show(); + $(".context_collapsed").show(); } }); - + /* fade flashes and alerts in automatically */ $(".alert").fadeOut(8000); } diff --git a/public/stylesheets/standard.css b/public/stylesheets/standard.css index 36b21d4d..fa8f893a 100644 --- a/public/stylesheets/standard.css +++ b/public/stylesheets/standard.css @@ -277,6 +277,12 @@ a.show_successors:hover, a.link_to_successors:hover {background-image: url(../im #navlist a:hover { color: #CCC; } +#develop-notify-bar { + line-height:0.5; + background-image: url(/images/construction.gif); + background-repeat: repeat-x; +} + #topbar { position: fixed; top: 0px; diff --git a/test/functional/todos_controller_test.rb b/test/functional/todos_controller_test.rb index 1abdfadc..e8003eac 100644 --- a/test/functional/todos_controller_test.rb +++ b/test/functional/todos_controller_test.rb @@ -492,7 +492,7 @@ class TodosControllerTest < ActionController::TestCase recurring_todo_1 = RecurringTodo.find(1) set_user_to_current_time_zone(recurring_todo_1.user) todo_1 = Todo.find_by_recurring_todo_id(1) - today = Time.now.at_midnight + today = Time.zone.now.at_midnight # change recurrence pattern to monthly and set show_from to today recurring_todo_1.target = 'show_from_date' @@ -644,4 +644,69 @@ class TodosControllerTest < ActionController::TestCase assert_select("div#notes_todo_#{todo.id} a", 'link me to onenote') assert_select("div#notes_todo_#{todo.id} a[href=onenote:///E:\\OneNote\\dir\\notes.one#PAGE&section-id={FD597D3A-3793-495F-8345-23D34A00DD3B}&page-id={1C95A1C7-6408-4804-B3B5-96C28426022B}&end]", 'link me to onenote') end + + def test_get_boolean_expression_from_parameters_of_tag_view_single_tag + login_as(:admin_user) + get :tag, :name => "single" + assert_equal true, assigns['single_tag'], "should recognize it is a single tag name" + assert_equal "single", assigns['tag_expr'][0][0], "should store the single tag" + assert_equal "single", assigns['tag_name'], "should store the single tag name" + end + + def test_get_boolean_expression_from_parameters_of_tag_view_multiple_tags + login_as(:admin_user) + get :tag, :name => "multiple", :and => "tags", :and1 => "present", :and2 => "here" + assert_equal false, assigns['single_tag'], "should recognize it has multiple tags" + assert_equal 4, assigns['tag_expr'].size, "should have 4 AND expressions" + end + + def test_get_boolean_expression_from_parameters_of_tag_view_multiple_tags_without_digitless_and + login_as(:admin_user) + get :tag, :name => "multiple", :and1 => "tags", :and2 => "present", :and3 => "here" + assert_equal false, assigns['single_tag'], "should recognize it has multiple tags" + assert_equal 4, assigns['tag_expr'].size, "should have 4 AND expressions" + end + + def test_get_boolean_expression_from_parameters_of_tag_view_multiple_ORs + login_as(:admin_user) + get :tag, :name => "multiple,tags,present" + assert_equal false, assigns['single_tag'], "should recognize it has multiple tags" + assert_equal 1, assigns['tag_expr'].size, "should have 1 expressions" + assert_equal 3, assigns['tag_expr'][0].size, "should have 3 ORs in 1st expression" + end + + def test_get_boolean_expression_from_parameters_of_tag_view_multiple_ORs_and_ANDS + login_as(:admin_user) + get :tag, :name => "multiple,tags,present", :and => "here,is,two", :and1=>"and,three" + assert_equal false, assigns['single_tag'], "should recognize it has multiple tags" + assert_equal 3, assigns['tag_expr'].size, "should have 3 expressions" + assert_equal 3, assigns['tag_expr'][0].size, "should have 3 ORs in 1st expression" + assert_equal 3, assigns['tag_expr'][1].size, "should have 3 ORs in 2nd expression" + assert_equal 2, assigns['tag_expr'][2].size, "should have 2 ORs in 3rd expression" + end + + def test_set_right_title + login_as(:admin_user) + + get :tag, :name => "foo" + assert_equal "foo", assigns['tag_title'] + get :tag, :name => "foo,bar", :and => "baz" + assert_equal "foo,bar AND baz", assigns['tag_title'] + end + + def test_set_default_tag + login_as(:admin_user) + + get :tag, :name => "foo" + assert_equal "foo", assigns['initial_tags'] + get :tag, :name => "foo,bar", :and => "baz" + assert_equal "foo", assigns['initial_tags'] + end + + def test_tag_text_feed_not_accessible_to_anonymous_user_without_token + login_as nil + get :tag, {:name => "foo", :format => "txt" } + assert_response 401 + end + end diff --git a/test/unit/todo_test.rb b/test/unit/todo_test.rb index 00460139..eb677699 100644 --- a/test/unit/todo_test.rb +++ b/test/unit/todo_test.rb @@ -9,7 +9,7 @@ class TodoTest < ActiveSupport::TestCase @not_completed2 = Todo.find(2).reload @completed = Todo.find(8).reload end - + # Test loading a todo item def test_load assert_kind_of Todo, @not_completed1 @@ -24,13 +24,13 @@ class TodoTest < ActiveSupport::TestCase assert_nil @not_completed1.completed_at assert_equal 1, @not_completed1.user_id end - + def test_completed assert_kind_of Todo, @completed assert @completed.completed? assert_not_nil @completed.completed_at end - + def test_completed_at_cleared_after_toggle_to_active assert_kind_of Todo, @completed assert @completed.completed? @@ -38,8 +38,8 @@ class TodoTest < ActiveSupport::TestCase assert @completed.active? assert_nil @completed.completed_at end - - + + # Validation tests # def test_validate_presence_of_description @@ -49,7 +49,7 @@ class TodoTest < ActiveSupport::TestCase assert_equal 1, @not_completed2.errors.count assert_equal "can't be blank", @not_completed2.errors.on(:description) end - + def test_validate_length_of_description assert_equal "Call dinosaur exterminator", @not_completed2.description @not_completed2.description = generate_random_string(101) @@ -57,7 +57,7 @@ class TodoTest < ActiveSupport::TestCase assert_equal 1, @not_completed2.errors.count assert_equal "is too long (maximum is 100 characters)", @not_completed2.errors.on(:description) end - + def test_validate_length_of_notes assert_equal "Ask him if I need to hire a skip for the corpses.", @not_completed2.notes @not_completed2.notes = generate_random_string(60001) @@ -74,7 +74,7 @@ class TodoTest < ActiveSupport::TestCase assert_equal 1, t.errors.count assert_equal "must be a date in the future", t.errors.on(:show_from) end - + def test_defer_an_existing_todo @not_completed2 assert_equal :active, @not_completed2.aasm_current_state @@ -82,20 +82,20 @@ class TodoTest < ActiveSupport::TestCase assert @not_completed2.save, "should have saved successfully" + @not_completed2.errors.to_xml assert_equal :deferred, @not_completed2.aasm_current_state end - + def test_create_a_new_deferred_todo user = users(:other_user) todo = user.todos.build todo.show_from = next_week todo.context_id = 1 - todo.description = 'foo' + todo.description = 'foo' assert todo.save, "should have saved successfully" + todo.errors.to_xml assert_equal :deferred, todo.aasm_current_state end def test_create_a_new_deferred_todo_by_passing_attributes user = users(:other_user) - todo = user.todos.build(:show_from => next_week, :context_id => 1, :description => 'foo') + todo = user.todos.build(:show_from => next_week, :context_id => 1, :description => 'foo') assert todo.save, "should have saved successfully" + todo.errors.to_xml assert_equal :deferred, todo.aasm_current_state end @@ -167,15 +167,15 @@ class TodoTest < ActiveSupport::TestCase t.reload assert_equal :deferred, t.aasm_current_state end - + def test_todo_is_not_starred assert !@not_completed1.starred? end - + def test_todo_2_is_not_starred assert !Todo.find(2).starred? end - + def test_todo_is_starred_after_starred_tag_is_added @not_completed1._add_tags('starred') assert @not_completed1.starred? @@ -185,7 +185,7 @@ class TodoTest < ActiveSupport::TestCase @not_completed1.toggle_star! assert @not_completed1.starred? end - + def test_todo_is_not_starred_after_toggle_starred_twice @not_completed1.toggle_star! @not_completed1.toggle_star! @@ -239,4 +239,101 @@ class TodoTest < ActiveSupport::TestCase assert_equal 2, @predecessor_array.size end + def test_finding_todos_with_a_tag + todo = @not_completed1 + todo.tag_list = "a, b, c" + todo.save! + + tag_a = Tag.find_by_name("a") + tag_b = Tag.find_by_name("b") + tag_c = Tag.find_by_name("c") + + todos_with_a = Todo.with_tag(tag_a) + assert 1, todos_with_a.count + assert_equal todo.description, todos_with_a.first.description + + todos_with_b = Todo.with_tag(tag_b) + assert 1, todos_with_b.count + assert_equal todo.id, todos_with_b.first.id + + todo2 = @not_completed2 + todo2.tag_list = "a, c, d" + todo2.save! + + tag_d = Tag.find_by_name("d") + + todos_with_a = Todo.with_tag(tag_a) + assert 2, todos_with_a.count + + todos_with_d = Todo.with_tag(tag_d) + assert 1, todos_with_a.count + end + + def test_finding_todos_with_more_tags_using_OR + todo1 = @not_completed1 + todo1.tag_list = "a, b, c" + todo1.save! + + todo2 = @not_completed2 + todo2.tag_list = "a, c, d" + todo2.save! + + tag_a = Tag.find_by_name("a") + tag_b = Tag.find_by_name("b") + tag_c = Tag.find_by_name("c") + tag_d = Tag.find_by_name("d") + + # overlapping tags + tag_ids = [tag_a.id, tag_c.id] + todos_with_a_or_c = Todo.with_tags(tag_ids) + assert 2, todos_with_a_or_c.count + + # non-overlapping tags + tag_ids = [tag_b.id, tag_d.id] + todos_with_b_or_d = Todo.with_tags(tag_ids) + assert 2, todos_with_b_or_d.count + end + + def test_finding_todos_with_more_tags_using_AND + todo1 = @not_completed1 + todo1.tag_list = "a, b, c" + todo1.save! + + todo2 = @not_completed2 + todo2.tag_list = "a, c, d" + todo2.save! + + tag_a_id = Tag.find_by_name("a").id + tag_b_id = Tag.find_by_name("b").id + + todos_with_a_and_b = Todo.with_tags([tag_a_id]).with_tags([tag_b_id]) + assert 1, todos_with_a_and_b.count + assert todo1.id, todos_with_a_and_b.first.id + end + + def test_finding_todos_with_more_tags_using_AND_and_OR + todo1 = @not_completed1 + todo1.tag_list = "a, b, c" + todo1.save! + + todo2 = @not_completed2 + todo2.tag_list = "a, c, d" + todo2.save! + + tag_a_id = Tag.find_by_name("a").id + tag_b_id = Tag.find_by_name("b").id + tag_c_id = Tag.find_by_name("c").id + + todos_with_aORc_and_b = Todo.with_tags([tag_a_id, tag_c_id]).with_tags([tag_b_id]) + assert 1, todos_with_aORc_and_b.count + assert todo1.id, todos_with_aORc_and_b.first.id + + # let todo2 fit the expression + todo2.tag_list = "a, b, r" + todo2.save! + todos_with_aORc_and_b = Todo.with_tags([tag_a_id, tag_c_id]).with_tags([tag_b_id]) + assert 2, todos_with_aORc_and_b.count + end + + end