fix #922. You can now mark a todo complete from the tickler. Also fixed some small aasm corner cases found by this change

This commit is contained in:
Reinier Balt 2011-08-18 17:15:00 +02:00
parent 367907eab2
commit 07b05d01f7
16 changed files with 276 additions and 147 deletions

View file

@ -240,12 +240,12 @@ class TodosController < ApplicationController
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 @@ class TodosController < ApplicationController
@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 @@ class TodosController < ApplicationController
@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 @@ 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
@ -1015,7 +1022,13 @@ class TodosController < ApplicationController
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 {

View file

@ -352,11 +352,21 @@ module TodosHelper
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 @@ module TodosHelper
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 @@ module TodosHelper
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?
}

View file

@ -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 <context, project>
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 @@ class Todo < ActiveRecord::Base
end
end
end
def save_predecessors
unless @predecessor_array.nil? # Only save predecessors if they changed
current_array = self.predecessors
@ -155,19 +161,19 @@ class Todo < ActiveRecord::Base
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 @@ class Todo < ActiveRecord::Base
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 @@ class Todo < ActiveRecord::Base
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 @@ class Todo < ActiveRecord::Base
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 @@ class Todo < ActiveRecord::Base
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 @@ class Todo < ActiveRecord::Base
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 @@ class Todo < ActiveRecord::Base
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 @@ class Todo < ActiveRecord::Base
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 @@ class Todo < ActiveRecord::Base
todo.project_id = project_id unless project_id.nil?
return todo
end
end

View file

@ -9,7 +9,7 @@ parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
<div id="<%= dom_id(todo) %>" class="item-container">
<div id="<%= dom_id(todo, 'line') %>" class="item-show">
<%= 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 %>
<ul class="sf-menu sf-item-menu">
<li style="z-index:<%=@z_index_counter%>"><%= image_tag "downarrow.png", :alt=> "" %>

View file

@ -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 -%>

View file

@ -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 -%>

View file

@ -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 })) : "" %>";
}
}

View file

@ -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

View file

@ -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"
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 <page>
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 <page>
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" |

View file

@ -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"

View file

@ -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 @@ Then /^I should see the container for context "([^"]*)"$/ do |context_name|
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 @@ Then /^I should not see "([^"]*)" in the context container for "([^"]*)"$/ do |t
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 @@ Then /^I should see "([^"]*)" in the deferred container$/ do |todo_description|
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,51 +116,6 @@ Then /^I should not see "([^"]*)" in the project container of "([^"]*)"$/ do |to
end
end
Then /^I should 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='completed_container']//div[@id='line_todo_#{todo.id}']"
wait_for :timeout => 5 do
selenium.is_element_present(xpath)
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
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
xpath = "//div[@id='hidden']//div[@id='line_todo_#{todo.id}']"
wait_for :timeout => 5 do
selenium.is_element_present(xpath)
end
end
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
xpath = "//div[@id='due_after_this_month']//div[@id='line_todo_#{todo.id}']"
wait_for :timeout => 5 do
selenium.is_element_present(xpath)
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
@ -156,6 +129,60 @@ Then /^I should see "([^"]*)" in project container for "([^"]*)"$/ do |todo_desc
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)
todo.should_not be_nil
xpath = "//div[@id='completed_container']//div[@id='line_todo_#{todo.id}']"
wait_for :timeout => 5 do
selenium.is_element_present(xpath)
end
end
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='completed_container']//div[@id='line_todo_#{todo.id}']"
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
xpath = "//div[@id='hidden']//div[@id='line_todo_#{todo.id}']"
wait_for :timeout => 5 do
selenium.is_element_present(xpath)
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
xpath = "//div[@id='due_after_this_month']//div[@id='line_todo_#{todo.id}']"
wait_for :timeout => 5 do
selenium.is_element_present(xpath)
end
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)

View file

@ -114,3 +114,16 @@ Then /^I should not see "([^"]*)" within the dependencies of "([^"]*)"$/ do |suc
!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

View file

@ -6,17 +6,7 @@ When /^I mark "([^"]*)" as complete$/ do |action_description|
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 @@ When /^I mark "([^"]*)" as uncompleted$/ do |action_description|
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|

View file

@ -60,7 +60,6 @@ Then /^I should see ([0-9]+) todos$/ do |count|
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 @@ Then /^I should see the todo "([^\"]*)"$/ do |todo_description|
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|

View file

@ -74,6 +74,7 @@ module NavigationHelpers
when /the integrations page/
integrations_path(options)
when /the tickler page/
@source_view = "deferred"
tickler_path(options)
when /the export page/
data_path(options)

View file

@ -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"