From 07b05d01f7b1a4b629a5ed558eae46f5f445e9eb Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Thu, 18 Aug 2011 17:15:00 +0200 Subject: [PATCH] fix #922. You can now mark a todo complete from the tickler. Also fixed some small aasm corner cases found by this change --- app/controllers/todos_controller.rb | 19 +++- app/helpers/todos_helper.rb | 27 ++++- app/models/todo.rb | 104 +++++++++--------- app/views/todos/_todo.html.erb | 2 +- app/views/todos/add_predecessor.js.erb | 4 +- app/views/todos/remove_predecessor.js.erb | 4 +- app/views/todos/toggle_check.js.erb | 31 ++++-- doc/CHANGELOG | 4 +- features/dependencies.feature | 48 +++++++- features/edit_a_todo.feature | 1 + features/step_definitions/container_steps.rb | 65 +++++++---- .../step_definitions/dependencies_steps.rb | 13 +++ features/step_definitions/todo_edit_steps.rb | 26 +---- features/step_definitions/todo_steps.rb | 7 +- features/support/paths.rb | 1 + features/tickler.feature | 9 ++ 16 files changed, 247 insertions(+), 118 deletions(-) diff --git a/app/controllers/todos_controller.rb b/app/controllers/todos_controller.rb index d7afdcc11..774197e35 100644 --- a/app/controllers/todos_controller.rb +++ b/app/controllers/todos_controller.rb @@ -240,12 +240,12 @@ def show def add_predecessor @source_view = params['_source_view'] || 'todo' @predecessor = current_user.todos.find(params['predecessor']) + @predecessors = @predecessor.predecessors @todo = current_user.todos.find(params['successor'], :include => Todo::DEFAULT_INCLUDES) @original_state = @todo.state unless @predecessor.completed? - # Add predecessor @todo.add_predecessor(@predecessor) - @todo.state = 'pending' + @todo.block! @saved = @todo.save @status_message = t('todos.added_dependency', :dependency => @predecessor.description) @@ -262,6 +262,7 @@ def remove_predecessor @source_view = params['_source_view'] || 'todo' @todo = current_user.todos.find(params['id'], :include => Todo::DEFAULT_INCLUDES) @predecessor = current_user.todos.find(params['predecessor']) + @predecessors = @predecessor.predecessors @successor = @todo @removed = @successor.remove_predecessor(@predecessor) determine_remaining_in_context_count @@ -277,14 +278,19 @@ def toggle_check @source_view = params['_source_view'] || 'todo' @original_item_due = @todo.due @original_item_was_deferred = @todo.deferred? + @original_item_was_pending = @todo.pending? @original_item_was_hidden = @todo.hidden? @original_item_context_id = @todo.context_id @original_item_project_id = @todo.project_id + @todo_was_completed_from_deferred_or_blocked_state = @original_item_was_deferred || @original_item_was_pending @saved = @todo.toggle_completion! + @todo_was_blocked_from_completed_state = @todo.pending? # since we toggled_completion the previous state was completed + # check if this todo has a related recurring_todo. If so, create next todo @new_recurring_todo = check_for_next_todo(@todo) if @saved + @predecessors = @todo.uncompleted_predecessors if @saved if @todo.completed? @pending_to_activate = @todo.activate_pending_todos @@ -1008,6 +1014,7 @@ def determine_remaining_in_context_count(context_id = @todo.context_id) 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 @@ -1015,7 +1022,13 @@ def determine_remaining_in_context_count(context_id = @todo.context_id) from.project { project_id = @project_changed ? @original_item_project_id : @todo.project_id @remaining_deferred_or_pending_count = current_user.projects.find(project_id).todos.deferred_or_blocked.count - @remaining_in_context = current_user.projects.find(project_id).todos.active.count + + if @todo_was_completed_from_deferred_or_blocked_state + @remaining_in_context = @remaining_deferred_or_pending_count + else + @remaining_in_context = current_user.projects.find(project_id).todos.active.count + end + @target_context_count = current_user.projects.find(project_id).todos.active.count } from.calendar { diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index 43f8242f0..732ca49e5 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -352,11 +352,21 @@ def empty_container_msg_div_id(todo = @todo || @successor) source_view do |page| page.project { - return "tickler-empty-nd" if @todo_was_deferred_from_active_state || @todo_was_blocked_from_active_state || @todo_was_destroyed_from_deferred_state || @todo_was_created_deferred + return "tickler-empty-nd" if + @todo_was_deferred_from_active_state || + @todo_was_blocked_from_active_state || + @todo_was_destroyed_from_deferred_state || + @todo_was_created_deferred || + @todo_was_blocked_from_completed_state return "p#{todo.project_id}empty-nd" } page.tag { - return "tickler-empty-nd" if @todo_was_deferred_from_active_state || @todo_was_destroyed_from_deferred_state || @todo_was_created_deferred + return "tickler-empty-nd" if + @todo_was_deferred_from_active_state || + @todo_was_blocked_from_active_state || + @todo_was_destroyed_from_deferred_state || + @todo_was_created_deferred || + @todo_was_blocked_from_completed_state return "hidden-empty-nd" if @todo.hidden? return "c#{todo.context_id}empty-nd" } @@ -368,14 +378,19 @@ def empty_container_msg_div_id(todo = @todo || @successor) return "c#{todo.context_id}empty-nd" end + def todo_was_removed_from_deferred_or_blocked_container + return @todo_was_activated_from_deferred_state || + @todo_was_activated_from_pending_state || + @todo_was_destroyed_from_deferred_or_pending_state || + @todo_was_completed_from_deferred_or_blocked_state + end + def show_empty_message_in_source_container container_id = "" source_view do |page| page.project { container_id = "p#{@original_item_project_id}empty-nd" if @remaining_in_context == 0 - container_id = "tickler-empty-nd" if ( - ( (@todo_was_activated_from_deferred_state || @todo_was_activated_from_pending_state || @todo_was_destroyed_from_deferred_or_pending_state) && @remaining_deferred_or_pending_count == 0) || - (@original_item_was_deferred && @remaining_deferred_or_pending_count == 0 && @todo.completed?) ) + container_id = "tickler-empty-nd" if todo_was_removed_from_deferred_or_blocked_container && @remaining_deferred_or_pending_count == 0 container_id = "empty-d" if @completed_count && @completed_count == 0 && !@todo.completed? } page.deferred { container_id = "c#{@original_item_context_id}empty-nd" if @remaining_in_context == 0 } @@ -383,7 +398,7 @@ def show_empty_message_in_source_container page.tag { container_id = "hidden-empty-nd" if (@remaining_hidden_count == 0 && !@todo.hidden? && @todo_hidden_state_changed) || (@remaining_hidden_count == 0 && @todo.completed? && @original_item_was_hidden) - container_id = "tickler-empty-nd" if (@todo_was_activated_from_deferred_state && @remaining_deferred_or_pending_count == 0) || + container_id = "tickler-empty-nd" if (todo_was_removed_from_deferred_or_blocked_container && @remaining_deferred_or_pending_count == 0) || (@original_item_was_deferred && @remaining_deferred_or_pending_count == 0 && (@todo.completed? || @tag_was_removed)) container_id = "empty-d" if @completed_count && @completed_count == 0 && !@todo.completed? } diff --git a/app/models/todo.rb b/app/models/todo.rb index 3a14830c9..5b9710a5b 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -7,7 +7,7 @@ class Todo < ActiveRecord::Base belongs_to :project belongs_to :user belongs_to :recurring_todo - + has_many :predecessor_dependencies, :foreign_key => 'predecessor_id', :class_name => 'Dependency', :dependent => :destroy has_many :successor_dependencies, :foreign_key => 'successor_id', :class_name => 'Dependency', :dependent => :destroy has_many :predecessors, :through => :successor_dependencies @@ -27,7 +27,7 @@ class Todo < ActiveRecord::Base named_scope :pending, :conditions => ['todos.state = ?', 'pending'] 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, + named_scope :hidden, :joins => :context, :conditions => ["todos.state = ? OR (contexts.hide = ? AND (todos.state = ? OR todos.state = ? OR todos.state = ?))", 'project_hidden', true, 'active', 'deferred', 'pending'] @@ -41,7 +41,7 @@ class Todo < ActiveRecord::Base named_scope :with_tag, lambda { |tag| {:joins => :taggings, :conditions => ["taggings.tag_id = ? ", tag.id] } } 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] } } + named_scope :completed_before, lambda { |date| {:conditions => ["todos.completed_at < ? ", date] } } STARRED_TAG_NAME = "starred" DEFAULT_INCLUDES = [ :project, :context, :tags, :taggings, :pending_successors, :uncompleted_predecessors, :recurring_todo ] @@ -52,76 +52,82 @@ class Todo < ActiveRecord::Base RE_PROJECT = /[^']+/ RE_PARTS = /'(#{RE_TODO})'\s<'(#{RE_CONTEXT})';\s'(#{RE_PROJECT})'>/ # results in array RE_SPEC = /'#{RE_TODO}'\s<'#{RE_CONTEXT}';\s'#{RE_PROJECT}'>/ # results in string - + include AASM aasm_column :state aasm_initial_state Proc.new { |t| (t.show_from && t.user && (t.show_from > t.user.date)) ? :deferred : :active} - - # when entering active state, also remove completed_at date. Looks like :exit - # of state completed is not run, see #679 + aasm_state :active aasm_state :project_hidden - aasm_state :completed, :enter => Proc.new { |t| t.completed_at = Time.zone.now }, :exit => Proc.new { |t| t.completed_at = nil } + aasm_state :completed, :enter => Proc.new { |t| t.completed_at = Time.zone.now }, :exit => Proc.new { |t| t.completed_at = nil} aasm_state :deferred, :exit => Proc.new { |t| t[:show_from] = nil } - aasm_state :pending + aasm_state :pending aasm_event :defer do transitions :to => :deferred, :from => [:active] end - + aasm_event :complete do - transitions :to => :completed, :from => [:active, :project_hidden, :deferred] + transitions :to => :completed, :from => [:active, :project_hidden, :deferred, :pending] end - + aasm_event :activate do - transitions :to => :active, :from => [:project_hidden, :completed, :deferred] - transitions :to => :active, :from => [:pending], :guard => :no_uncompleted_predecessors_or_deferral? + transitions :to => :active, :from => [:project_hidden, :deferred] + transitions :to => :active, :from => [:completed], :guard => :no_uncompleted_predecessors? + transitions :to => :active, :from => [:pending], :guard => :no_uncompleted_predecessors_or_deferral? + transitions :to => :pending, :from => [:completed], :guard => :uncompleted_predecessors? transitions :to => :deferred, :from => [:pending], :guard => :no_uncompleted_predecessors? end - + aasm_event :hide do transitions :to => :project_hidden, :from => [:active, :deferred] end - + aasm_event :unhide do transitions :to => :deferred, :from => [:project_hidden], :guard => Proc.new{|t| !t.show_from.blank? } transitions :to => :active, :from => [:project_hidden] end - + aasm_event :block do transitions :to => :pending, :from => [:active, :deferred] end - + attr_protected :user # Description field can't be empty, and must be < 100 bytes Notes must be < # 60,000 bytes (65,000 actually, but I'm being cautious) validates_presence_of :description validates_length_of :description, :maximum => 100 - validates_length_of :notes, :maximum => 60000, :allow_nil => true + validates_length_of :notes, :maximum => 60000, :allow_nil => true validates_presence_of :show_from, :if => :deferred? validates_presence_of :context - + def initialize(*args) super(*args) @predecessor_array = nil # Used for deferred save of predecessors @removed_predecessors = nil end - + def no_uncompleted_predecessors_or_deferral? - return (show_from.blank? or Time.zone.now > show_from and uncompleted_predecessors.empty?) + no_deferral = show_from.blank? or Time.zone.now > show_from + no_uncompleted_predecessors = uncompleted_predecessors.all(true).empty? + return (no_deferral && no_uncompleted_predecessors) end - + def no_uncompleted_predecessors? - return uncompleted_predecessors.empty? + return uncompleted_predecessors.all(true).empty? end - + + def uncompleted_predecessors? + return !uncompleted_predecessors.all(true).empty? + end + # Returns a string with description def specification project_name = self.project.is_a?(NullProject) ? "(none)" : self.project.name return "\'#{self.description}\' <\'#{self.context.title}\'; \'#{project_name}\'>" end - + def validate if !show_from.blank? && show_from < user.date errors.add("show_from", I18n.t('models.todo.error_date_must_be_future')) @@ -133,7 +139,7 @@ def validate end end end - + def save_predecessors unless @predecessor_array.nil? # Only save predecessors if they changed current_array = self.predecessors @@ -155,19 +161,19 @@ def save_predecessors logger.error "Could not find #{todo.description}" # Unexpected since validation passed end end - end + end end def removed_predecessors return @removed_predecessors end - + def remove_predecessor(predecessor) # remove predecessor and activate myself self.predecessors.delete(predecessor) self.activate! end - + # Returns true if t is equal to self or a successor of self def is_successor?(todo) if self == todo @@ -183,11 +189,11 @@ def is_successor?(todo) end return false end - + def has_pending_successors return !pending_successors.empty? end - + def has_tag?(tag) return self.tags.select{|t| t.name==tag }.size > 0 end @@ -208,23 +214,23 @@ def update_state_from_project end self.save! end - + def toggle_completion! return completed? ? activate! : complete! end - + def show_from self[:show_from] end - + def show_from=(date) # parse Date objects into the proper timezone date = user.at_midnight(date) if (date.is_a? Date) - # show_from needs to be set before state_change because of "bug" in aasm. + # show_from needs to be set before state_change because of "bug" in aasm. # If show_from is not set, the todo will not validate and thus aasm will not save # (see http://stackoverflow.com/questions/682920/persisting-the-state-column-on-transition-using-rubyist-aasm-acts-as-state-machi) - self[:show_from] = date + self[:show_from] = date activate! if deferred? && date.blank? defer! if active? && !date.blank? && date > user.date @@ -235,18 +241,18 @@ def show_from=(date) def project original_project.nil? ? Project.null_object : original_project end - + def self.feed_options(user) { :title => 'Tracks Actions', :description => "Actions for #{user.display_name}" } end - + def starred? tags.any? {|tag| tag.name == STARRED_TAG_NAME} end - + def toggle_star! self.starred= !starred? end @@ -277,19 +283,19 @@ def add_predecessor_list(predecessor_list) return @predecessor_array end - + def add_predecessor(t) @predecessor_array = predecessors @predecessor_array << t end - + # activate todos that should be activated if the current todo is completed def activate_pending_todos pending_todos = successors.find_all {|t| t.uncompleted_predecessors.empty?} pending_todos.each {|t| t.activate! } return pending_todos end - + # Return todos that should be blocked if the current todo is undone def block_successors active_successors = successors.find_all {|t| t.active? or t.deferred?} @@ -302,13 +308,13 @@ def raw_notes=(value) end # Rich Todo API - + def self.from_rich_message(user, default_context_id, description, notes) fields = description.match(/([^>@]*)@?([^>]*)>?(.*)/) description = fields[1].strip context = fields[2].strip project = fields[3].strip - + context = nil if context == "" project = nil if project == "" @@ -318,11 +324,11 @@ def self.from_rich_message(user, default_context_id, description, notes) found_context = user.contexts.find_by_namepart(context) if found_context.nil? context_id = found_context.id unless found_context.nil? end - + unless user.contexts.exists? context_id raise(CannotAccessContext, "Cannot access a context that does not belong to this user.") end - + project_id = nil unless(project.blank?) if(project[0..3].downcase == "new:") @@ -335,7 +341,7 @@ def self.from_rich_message(user, default_context_id, description, notes) end project_id = found_project.id unless found_project.nil? end - + todo = user.todos.build todo.description = description todo.raw_notes = notes @@ -343,5 +349,5 @@ def self.from_rich_message(user, default_context_id, description, notes) todo.project_id = project_id unless project_id.nil? return todo end - + end diff --git a/app/views/todos/_todo.html.erb b/app/views/todos/_todo.html.erb index 4ca9bc23d..0f95e258c 100644 --- a/app/views/todos/_todo.html.erb +++ b/app/views/todos/_todo.html.erb @@ -9,7 +9,7 @@ parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
<%= remote_star_icon(todo) %> - <%= remote_toggle_checkbox(todo) unless source_view_is :deferred %> + <%= remote_toggle_checkbox(todo) %> <%= remote_edit_button(todo) unless suppress_edit_button %>
  • <%= image_tag "downarrow.png", :alt=> "" %> diff --git a/app/views/todos/add_predecessor.js.erb b/app/views/todos/add_predecessor.js.erb index 69af584bc..608e3d9a0 100644 --- a/app/views/todos/add_predecessor.js.erb +++ b/app/views/todos/add_predecessor.js.erb @@ -1,6 +1,6 @@ <% if !@saved if @predecessor.completed? -%> - TracksPages.page_notify('error', "<%= t('todos.cannot_add_dependency_to_completed_todo') %>", 8); + TracksPages.page_notify('error', "<%= t('todos.cannot_add_dependency_to_completed_todo') %>", 8); $('#<%=dom_id(@todo)%>').html(html_for_todo()); <% else -%> TracksPages.page_notify('error', "<%= t('todos.unable_to_add_dependency') %>", 8); @@ -29,7 +29,7 @@ function show_in_tickler_box() { function regenerate_predecessor_family() { <% - parents = @predecessor.predecessors + parents = @predecessors until parents.empty? parent = parents.pop parents += parent.predecessors -%> diff --git a/app/views/todos/remove_predecessor.js.erb b/app/views/todos/remove_predecessor.js.erb index dbcf67bd4..e186991bf 100644 --- a/app/views/todos/remove_predecessor.js.erb +++ b/app/views/todos/remove_predecessor.js.erb @@ -1,7 +1,7 @@ <% # TODO: lots of overlap with add_predecessor --> helpers? if @removed -%> TracksPages.page_notify('notice', "<%= t('todos.removed_predecessor', :successor => @successor.description, :predecessor => @predecessor.description) %>", 8); - + replace_updated_predecessor(); regenerate_predecessor_family(); update_successor(); @@ -15,7 +15,7 @@ function replace_updated_predecessor() { function regenerate_predecessor_family() { <% - parents = @predecessor.predecessors + parents = @predecessors until parents.empty? parent = parents.pop parents += parent.predecessors -%> diff --git a/app/views/todos/toggle_check.js.erb b/app/views/todos/toggle_check.js.erb index 6a170544b..ed94e0304 100644 --- a/app/views/todos/toggle_check.js.erb +++ b/app/views/todos/toggle_check.js.erb @@ -7,15 +7,16 @@ animation = [] animation << "remove_todo" if @todo.completed? - animation << "add_to_completed_container" unless source_view_is(:calendar) + animation << "add_to_completed_container" unless source_view_is_one_of(:calendar, :deferred) animation << "add_new_recurring_todo" animation << "activate_pending_todos" animation << "remove_source_container" else animation << "add_todo_to_context" unless source_view_is(:done) animation << "block_predecessors" - end - animation << "update_empty_container" if source_view_is_one_of(:tag, :todo) -%> + end + animation << "update_empty_container" if source_view_is_one_of(:tag, :todo) + animation << "regenerate_predecessor_family" -%> <%= render_animation(animation) %> TracksPages.set_page_badge(<%= @down_count %>); <% end -%> @@ -73,7 +74,7 @@ function add_todo_to_context(next_steps) { function add_new_recurring_todo(next_steps) { <% # show new todo if the completed todo was recurring if @todo.from_recurring_todo? - unless @new_recurring_todo.nil? || @new_recurring_todo.deferred? -%> + unless @new_recurring_todo.nil? || (@new_recurring_todo.deferred? && !source_view_is(:deferred)) -%> $('#<%= item_container_id(@new_recurring_todo) %>').append(html_for_recurring_todo()); $('#c<%= @new_recurring_todo.context_id %>').fadeIn(500, function() { highlight_updated_recurring_todo(next_steps); @@ -111,7 +112,7 @@ function highlight_updated_todo(next_steps) { function activate_pending_todos(next_steps) { <% # Activate pending todos that are successors of the completed - if @saved && @pending_to_activate + if @saved && @pending_to_activate # do not render the js in case of an error or if no todos to activate @pending_to_activate.each do |t| html = escape_javascript(render(:partial => t, :locals => { :parent_container_type => parent_container_type })) @@ -124,7 +125,7 @@ function activate_pending_todos(next_steps) { }); <% else -%> $('#<%= item_container_id(t) %>').append("<%= html%>"); - <% end -%> + <% end -%> TodoItems.highlight_todo('#<%= dom_id(t)%>'); <% end -%> <% end -%> @@ -132,7 +133,7 @@ function activate_pending_todos(next_steps) { } function block_predecessors(next_steps) { - <% # Activate pending todos that are successors of the completed + <% # Block active todos that are successors of the uncompleted if @saved && @active_to_block # do not render the js in case of an error or if no todos to block @active_to_block.each do |t| %> @@ -148,6 +149,20 @@ function block_predecessors(next_steps) { next_steps.go(); } +function regenerate_predecessor_family(next_steps) { +<% +if @predecessors + parents = @predecessors + until parents.empty? + parent = parents.pop + parents += parent.predecessors -%> + $('#<%= dom_id(parent) %>').html("<%= escape_javascript(render(:partial => parent, :locals => { :parent_container_type => parent_container_type })) %>"); +<%end +end +-%> + next_steps.go(); +} + function remove_source_container(next_steps) { <% if (@remaining_in_context == 0 && source_view_is_one_of(:todo, :tag)) # remove context with deleted todo @@ -170,4 +185,4 @@ function html_for_todo() { function html_for_recurring_todo() { return "<%= @saved ? escape_javascript(render(:partial => @new_recurring_todo, :locals => { :parent_container_type => parent_container_type })) : "" %>"; -} +} \ No newline at end of file diff --git a/doc/CHANGELOG b/doc/CHANGELOG index f571653cc..d43aa373e 100644 --- a/doc/CHANGELOG +++ b/doc/CHANGELOG @@ -21,11 +21,13 @@ cause new actions not to appear! This version of tracks has moved to a new place on github. Also the wiki moved to github, see the changed URLs above. -New features: +New and changed features: 1. redesign of the completed todos: a new overview page. Also all context and project pages have a link to their completed actions 2. New locales (es and fr) and updated locales (de, nl) 3. You can star an action right from the form to add a new action +4. redesign of preferences page +5. You can now mark an action complete from the tickler Under the hood: 1. Upgraded rails to 2.3.12, jquery to 1.6.2 and jquery-ui to 1.8.14 diff --git a/features/dependencies.feature b/features/dependencies.feature index 1f75bf47e..c3e21b47d 100644 --- a/features/dependencies.feature +++ b/features/dependencies.feature @@ -122,4 +122,50 @@ Feature: dependencies When I go to the "dependencies" project And I drag "test 1" to "test 3" Then I should see an error flash message saying "Cannot add this action as a dependency to a completed action!" - And I should see "test 1" in project container for "dependencies" \ No newline at end of file + And I should see "test 1" in project container for "dependencies" + + @selenium + Scenario Outline: Marking a successor as complete will update predecessor + Given I have a context called "@pc" + And I have a project "dependencies" that has the following todos + | description | context | completed | tags | + | test 1 | @pc | no | bla | + | test 2 | @pc | no | bla | + | test 3 | @pc | yes | bla | + When I go to the + And I drag "test 1" to "test 2" + When I expand the dependencies of "test 2" + Then I should see "test 1" within the dependencies of "test 2" + And I should see "test 1" in the deferred container + When I mark "test 1" as complete + Then I should see that "test 2" does not have dependencies + And I should see "test 1" in the completed container + + Scenarios: + | page | + | "dependencies" project | + | tag page for "bla" | + + @selenium + Scenario Outline: Marking a successor as active will update predecessor + Given I have a context called "@pc" + And I have a project "dependencies" that has the following todos + | description | context | completed | tags | + | test 1 | @pc | no | bla | + | test 2 | @pc | no | bla | + | test 3 | @pc | yes | bla | + When I go to the + And I drag "test 1" to "test 2" + Then I should see "test 1" in the deferred container + When I mark "test 1" as complete + And I should see "test 1" in the completed container + And I should see that "test 2" does not have dependencies + When I mark the complete todo "test 1" active + Then I should not see "test 1" in the completed container + And I should see "test 1" in the deferred container + And I should see "test 1" within the dependencies of "test 2" + + Scenarios: + | page | + | "dependencies" project | + | tag page for "bla" | \ No newline at end of file diff --git a/features/edit_a_todo.feature b/features/edit_a_todo.feature index 42af3c584..b6fce909a 100644 --- a/features/edit_a_todo.feature +++ b/features/edit_a_todo.feature @@ -35,6 +35,7 @@ Feature: Edit a next action from every page And I should see "delete me" in the context container for "@home" When I mark "delete me" as complete Then I should not see the container for context "@home" + And I should see "delete me" in the completed container When I mark "delete me" as uncompleted Then I should see the container for context "@home" When I edit the context of "delete me" to "@pc" diff --git a/features/step_definitions/container_steps.rb b/features/step_definitions/container_steps.rb index 2c1dae208..ba2e95996 100644 --- a/features/step_definitions/container_steps.rb +++ b/features/step_definitions/container_steps.rb @@ -1,3 +1,5 @@ +####### Context ####### + Then /^I should not see the context "([^"]*)"$/ do |context_name| context = @current_user.contexts.find_by_name(context_name) context.should_not be_nil @@ -23,10 +25,11 @@ context = @current_user.contexts.find_by_name(context_name) context.should_not be_nil - xpath = "xpath=//div[@id='c#{context.id}']" + xpath = "//div[@id='c#{context.id}']" - selenium.wait_for_element(xpath, :timeout_in_seconds => 5) - selenium.is_visible(xpath).should be_true + wait_for :timeout => 5 do + selenium.is_visible(xpath) + end end Then /^the container for the context "([^"]*)" should be visible$/ do |context_name| @@ -60,6 +63,8 @@ end end +####### Deferred ####### + Then /^I should see "([^"]*)" in the deferred container$/ do |todo_description| todo = @current_user.todos.find_by_description(todo_description) todo.should_not be_nil @@ -71,6 +76,19 @@ end end +Then /^I should not see "([^"]*)" in the deferred container$/ do |todo_description| + todo = @current_user.todos.find_by_description(todo_description) + todo.should_not be_nil + + xpath = "//div[@id='tickler']//div[@id='line_todo_#{todo.id}']" + + wait_for :timeout => 5 do + !selenium.is_element_present(xpath) + end +end + +####### Project ####### + Then /^I should see "([^"]*)" in the action container$/ do |todo_description| todo = @current_user.todos.find_by_description(todo_description) todo.should_not be_nil @@ -98,6 +116,20 @@ end end +Then /^I should see "([^"]*)" in project container for "([^"]*)"$/ do |todo_description, project_name| + todo = @current_user.todos.find_by_description(todo_description) + todo.should_not be_nil + + project = @current_user.projects.find_by_name(project_name) + project.should_not be_nil + + xpath = "//div[@id='p#{project.id}items']//div[@id='line_todo_#{todo.id}']" + + selenium.wait_for_element("xpath=#{xpath}", :timeout_in_seconds => 5) + selenium.is_visible(xpath).should be_true +end + +####### Completed ####### Then /^I should see "([^"]*)" in the completed container$/ do |todo_description| todo = @current_user.todos.find_by_description(todo_description) @@ -110,17 +142,21 @@ end end -Then /^I should not see "([^"]*)" in the deferred container$/ do |todo_description| +Then /^I should not see "([^"]*)" in the completed container$/ do |todo_description| todo = @current_user.todos.find_by_description(todo_description) todo.should_not be_nil - xpath = "//div[@id='tickler']//div[@id='line_todo_#{todo.id}']" + xpath = "//div[@id='completed_container']//div[@id='line_todo_#{todo.id}']" - wait_for :timeout => 5 do - !selenium.is_element_present(xpath) + if selenium.is_element_present(xpath) + wait_for :timeout => 5 do + !selenium.is_element_present(xpath) + end end end +####### Hidden ####### + Then /^I should see "([^"]*)" in the hidden container$/ do |todo_description| todo = @current_user.todos.find_by_description(todo_description) todo.should_not be_nil @@ -132,6 +168,8 @@ end end +####### Calendar ####### + Then /^I should see "([^"]*)" in the due next month container$/ do |todo_description| todo = @current_user.todos.find_by_description(todo_description) todo.should_not be_nil @@ -143,18 +181,7 @@ end end -Then /^I should see "([^"]*)" in project container for "([^"]*)"$/ do |todo_description, project_name| - todo = @current_user.todos.find_by_description(todo_description) - todo.should_not be_nil - - project = @current_user.projects.find_by_name(project_name) - project.should_not be_nil - - xpath = "//div[@id='p#{project.id}items']//div[@id='line_todo_#{todo.id}']" - - selenium.wait_for_element("xpath=#{xpath}", :timeout_in_seconds => 5) - selenium.is_visible(xpath).should be_true -end +####### Repeat patterns ####### Then /^I should see "([^"]*)" in the active recurring todos container$/ do |repeat_pattern| repeat = @current_user.recurring_todos.find_by_description(repeat_pattern) diff --git a/features/step_definitions/dependencies_steps.rb b/features/step_definitions/dependencies_steps.rb index 3a30cd918..2dd19e675 100644 --- a/features/step_definitions/dependencies_steps.rb +++ b/features/step_definitions/dependencies_steps.rb @@ -114,3 +114,16 @@ !selenium.is_element_present(xpath) end end + +Then /^I should see that "([^"]*)" does not have dependencies$/ do |todo_description| + todo = @current_user.todos.find_by_description(todo_description) + todo.should_not be_nil + dependencies_icon = "//div[@id='line_todo_#{todo.id}']/div/a[@class='show_successors']/img" + + if selenium.is_element_present(dependencies_icon) + wait_for :timeout => 5 do + !selenium.is_element_present(dependencies_icon) + end + end +end + diff --git a/features/step_definitions/todo_edit_steps.rb b/features/step_definitions/todo_edit_steps.rb index be661655b..fb2db807f 100644 --- a/features/step_definitions/todo_edit_steps.rb +++ b/features/step_definitions/todo_edit_steps.rb @@ -6,17 +6,7 @@ check "mark_complete_#{todo.id}" - todo_container = "fail" # fail this test if @source_view is wrong - todo_container = "p#{todo.project_id}items" if @source_view=="project" - todo_container = "c#{todo.context_id}items" if @source_view=="context" || @source_view=="todos" || @source_view=="tag" - todo_container = "tickler_container" if @source_view=="stats" - - # container should be there - selenium.is_element_present("//div[@id='#{todo_container}']").should be_true - - wait_for :timeout => 5 do - !selenium.is_element_present("//div[@id='#{todo_container}']//div[@id='line_todo_#{todo.id}']") - end + wait_for_ajax end When /^I mark "([^"]*)" as uncompleted$/ do |action_description| @@ -25,19 +15,7 @@ check "mark_complete_#{todo.id}" - todo_container = "fail" # fail this test if @source_view is wrong - todo_container = "p#{todo.project_id}items" if @source_view=="project" - todo_container = "c#{todo.context_id}items" if @source_view=="context" || @source_view=="todos" || @source_view=="tag" - - todo_container.should_not == "fail" unless @source_view=="done" - - unless @source_view=="done" - wait_for :timeout => 5 do - selenium.is_element_present("//div[@id='#{todo_container}']//div[@id='line_todo_#{todo.id}']") - end - else - wait_for_ajax - end + wait_for_ajax end When /^I mark the complete todo "([^"]*)" active$/ do |action_description| diff --git a/features/step_definitions/todo_steps.rb b/features/step_definitions/todo_steps.rb index 32783ac9c..1ef86ff1a 100644 --- a/features/step_definitions/todo_steps.rb +++ b/features/step_definitions/todo_steps.rb @@ -60,7 +60,6 @@ end Then /^there should not be an error$/ do - sleep(5) # form should be gone and thus no errors visible wait_for :timeout => 5 do !selenium.is_visible("edit_todo_#{@dep_todo.id}") @@ -72,7 +71,11 @@ end Then /^I should not see the todo "([^\"]*)"$/ do |todo_description| - selenium.is_element_present("//span[.=\"#{todo_description}\"]").should be_false + if selenium.is_element_present("//span[.=\"#{todo_description}\"]") + wait_for :timeout => 5 do + !selenium.is_element_present("//span[.=\"#{todo_description}\"]") + end + end end Then /^the number of actions should be (\d+)$/ do |count| diff --git a/features/support/paths.rb b/features/support/paths.rb index badf8f9c9..52b32975a 100644 --- a/features/support/paths.rb +++ b/features/support/paths.rb @@ -74,6 +74,7 @@ def path_to(page_name) when /the integrations page/ integrations_path(options) when /the tickler page/ + @source_view = "deferred" tickler_path(options) when /the export page/ data_path(options) diff --git a/features/tickler.feature b/features/tickler.feature index 56376b25b..446323bed 100644 --- a/features/tickler.feature +++ b/features/tickler.feature @@ -57,3 +57,12 @@ Feature: Manage deferred todos When I go to the tickler page Then I should see "not yet now" And I should not see "now is a good time" + + @selenium + Scenario: I can mark an action complete from the tickler + Given I have a deferred todo "not yet now" + When I go to the tickler page + And I mark "not yet now" as complete + Then I should not see "not yet now" + When I go to the done page + Then I should see "not yet now"