* Converted Todo to acts_as_state_machine. It's states are active, deferred, completed, and project_hidden. This replaces the old single inheritance model of Immediate and Deferred. Also renamed todo.completed to todo.completed_at for clarity

* Consolidated toggle_check handling to todo_controller and rjs  
* Introduced user preference to show/hide hidden projects section in sidebar
* Fixed a bug in parse_date_per_user_prefs that was causing due dates to be set in the todo model as Times and not Dates
* Upgraded ARTS plugin
* This changeset includes migrations, so remember to db:migrate.
* Lots of code changes here, so bug reports will be gratefully accepted!



git-svn-id: http://www.rousette.org.uk/svn/tracks-repos/trunk@343 a4c988fc-2ded-0310-b66e-134b36920a42
This commit is contained in:
lukemelia 2006-11-15 09:05:07 +00:00
parent 883bcb30bb
commit c51587e422
47 changed files with 487 additions and 301 deletions

View file

@ -83,23 +83,28 @@ class ApplicationController < ActionController::Base
def parse_date_per_user_prefs( s )
return nil if s == ''
Chronic.parse(s)
Chronic.parse(s).to_date
end
def init_data_for_sidebar
@projects = @user.projects
@contexts = @user.contexts
init_not_done_counts
if @prefs.show_hidden_projects_in_sidebar
init_project_hidden_todo_counts(['project'])
end
end
def init_not_done_counts(parents = ['project','context'])
parents.each do |parent|
eval("@#{parent}_not_done_counts = Todo.count(:all,
:conditions => ['todos.user_id = ? and todos.type = ? and todos.done = ? and (projects.state != ? or todos.project_id is ?)',
@user.id, \"Immediate\", false, \"hidden\", nil],
:joins => 'LEFT JOIN projects on projects.id = todos.project_id',
:group => :#{parent}_id)")
eval("@#{parent}_not_done_counts = Todo.count(:conditions => ['user_id = ? and state = ?', @user.id, 'active'], :group => :#{parent}_id)")
end
end
end
def init_project_hidden_todo_counts(parents = ['project','context'])
parents.each do |parent|
eval("@#{parent}_project_hidden_todo_counts = Todo.count(:conditions => ['user_id = ? and state = ?', @user.id, 'project_hidden'], :group => :#{parent}_id)")
end
end
end

View file

@ -102,29 +102,6 @@ class ContextController < ApplicationController
redirect_to :controller => 'todo', :action => 'list'
end
end
# Toggles the 'done' status of the action
#
def toggle_check
self.init
@item = check_user_return_item
@item.toggle!('done')
@item.completed = Time.now() # For some reason, the before_save in todo.rb stopped working
@saved = @item.save
if @saved
@down_count = Todo.find(:all, :conditions => ["todos.user_id = ? and todos.done = ? and todos.context_id IN (?)", @user.id, false, @item.context_id]).size.to_s
@done_count = Todo.find(:all, :conditions => ["todos.user_id = ? and todos.done = ? and todos.context_id IN (?)", @user.id, true, @item.context_id]).size.to_s
end
return if request.xhr?
if @saved
flash[:notice] = "The action <strong>'#{@item.description}'</strong> was marked as <strong>#{@item.done? ? 'complete' : 'incomplete' }</strong>"
else
flash[:notice] = "The action <strong>'#{@item.description}'</strong> was NOT marked as <strong>#{@item.done? ? 'complete' : 'incomplete' } due to an error on the server.</strong>"
end
redirect_to :action => "list"
end
# Edit the details of the context
#
@ -210,11 +187,9 @@ class ContextController < ApplicationController
# If we exclude completed projects, then we can't display them in the sidebar
# if the user sets the preference for them to be shown
# @projects = @user.projects.reject { |x| x.completed? }
@projects = @user.projects
@contexts = @user.contexts
init_data_for_sidebar
@todos = @user.todos
@done = Todo.find(:all, :conditions => ["todos.user_id = ? and todos.done = ?", @user.id, true], :include => [:project], :order => "completed DESC")
init_not_done_counts
@done = @user.todos.find_in_state(:all, :completed, :order => "todos.completed_at DESC")
end
def init_todos
@ -224,10 +199,7 @@ class ContextController < ApplicationController
# TODO: Temporarily doing this search manually until I can work out a way
# to do the same thing using not_done_todos acts_as_todo_container method
# Hides actions in hidden projects from context.
@not_done_todos = Todo.find(:all,
:conditions => ["todos.context_id = ? and todos.done = ? and todos.type = ? and (projects.state != ? or todos.project_id is ?)", @context.id, false, "Immediate", "hidden", nil],
:order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC",
:include => [:project])
@not_done_todos = @context.todos.find_in_state(:all, :active, :order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC")
@count = @not_done_todos.size
end

View file

@ -12,16 +12,16 @@ class DeferredController < ApplicationController
def index
@source_view = 'deferred'
init_projects_and_contexts
init_not_done_counts
init_data_for_sidebar
@page_title = "TRACKS::Tickler"
@tickles = @user.todos.find(:all, :conditions => ['type = ?', "Deferred"], :order => "show_from ASC")
@tickles = @user.todos.find_in_state(:all, :deferred, :order => "show_from ASC")
@count = @tickles.size
end
def create
@source_view = 'deferred'
@item = Deferred.new
@item = Todo.new
@item.defer!
@item.attributes = params["todo"]
if params["todo"]["show_from"]
@item.show_from = parse_date_per_user_prefs(params["todo"]["show_from"])
@ -37,7 +37,7 @@ class DeferredController < ApplicationController
@saved = @item.save
if @saved
@up_count = @user.todos.count(['type = ?', "Deferred"])
@up_count = @user.todos.count_in_state(:deferred)
end
respond_to do |wants|
@ -84,7 +84,7 @@ class DeferredController < ApplicationController
redirect_to :action => "index"
end
wants.js do
@down_count = @user.todos.count(['type = ?', "Deferred"]) if @saved
@down_count = @user.todos.count_in_state(:deferred) if @saved
render
end
wants.xml { render :xml => @item.to_xml( :root => 'todo', :except => :user_id ) }
@ -101,30 +101,22 @@ class DeferredController < ApplicationController
end
end
# Check for any due tickler items, change them to type Immediate.
# Check for any due tickler items, activate them
# Called by periodically_call_remote
def check_tickler
now = Date.today()
@due_tickles = @user.todos.find(:all, :conditions => ['type = ? AND (show_from < ? OR show_from = ?)', "Deferred", now, now ], :order => "show_from ASC")
# Change the due tickles to type "Immediate"
@due_tickles = @user.todos.find_in_state(:all, :deferred, :conditions => ['show_from < ? OR show_from = ?', now, now ], :order => "show_from ASC")
# Change the due tickles to active
@due_tickles.each do |t|
t[:type] = "Immediate"
t.show_from = nil
t.save_with_validation(false)
t.activate!
t.save
end
respond_to do |wants|
wants.html { redirect_to :controller => 'todo', :action => 'index' }
wants.js
end
end
protected
def init_projects_and_contexts
@projects = @user.projects
@contexts = @user.contexts
end
private
def check_user_return_item

View file

@ -84,9 +84,9 @@ protected
options = Hash.new
if params.key?('done')
condition_builder.add 'todos.done = ?', true
condition_builder.add 'todos.state = ?', 'completed'
else
condition_builder.add 'todos.done = ?', false
condition_builder.add 'todos.state = ?', 'active'
end
if params.key?('limit')
@ -107,7 +107,7 @@ protected
if params.key?('done')
done_in_last = params['done'].to_i
condition_builder.add('todos.completed >= ?', done_in_last.days.ago)
condition_builder.add('todos.completed_at >= ?', done_in_last.days.ago)
@title << " actions completed"
@description << " in the last #{done_in_last.to_s} days"
end

View file

@ -16,9 +16,9 @@ class MobileController < ApplicationController
self.init
@page_title = @desc = "All actions"
@todos_pages, @todos = paginate( :todos, :order => 'due IS NULL, due ASC, created_at ASC',
:conditions => ['user_id = ? and type = ? and done = ?', @user.id, "Immediate", false],
:conditions => ['user_id = ? and state = ?', @user.id, "active"],
:per_page => 6 )
@count = @all_todos.reject { |x| x.done? || x.context.hide? }.size
@count = @all_todos.reject { |x| !x.active? || x.context.hide? }.size
end
def detail
@ -32,10 +32,11 @@ class MobileController < ApplicationController
@item = check_user_return_item
else
if params[:item][:"show_from(1i)"] == ""
@item = Immediate.create(params[:item]) if params[:item]
@item = Todo.create(params[:item]) if params[:item]
else
@item = Deferred.create(params[:item]) if params[:item]
end
@item = Todo.create(params[:item]) if params[:item]
@item.defer!
end
end
@item.user_id = @user.id
@ -64,14 +65,14 @@ class MobileController < ApplicationController
@context = Context.find( params[:context][:id] )
@page_title = @desc = "#{@context.name}"
@todos_pages, @todos = paginate( :todos, :order => 'due IS NULL, due ASC, created_at ASC',
:conditions => ['user_id = ? and type = ? and done = ? and context_id = ?', @user.id, "Immediate", false, @context.id], :per_page => 6 )
@count = @all_todos.reject { |x| x.done? || x.context_id != @context.id }.size
:conditions => ['user_id = ? and state = ? and context_id = ?', @user.id, "active", @context.id], :per_page => 6 )
@count = @all_todos.reject { |x| x.completed? || x.context_id != @context.id }.size
when 'project'
@project = Project.find( params[:project][:id] )
@page_title = @desc = "#{@project.name}"
@todos_pages, @todos = paginate( :todos, :order => 'due IS NULL, due ASC, created_at ASC',
:conditions => ['user_id = ? and type = ? and done = ? and project_id = ?', @user.id, "Immediate", false, @project.id], :per_page => 6 )
@count = @all_todos.reject { |x| x.done? || x.project_id != @project.id }.size
:conditions => ['user_id = ? and state = ? and project_id = ?', @user.id, "active", @project.id], :per_page => 6 )
@count = @all_todos.reject { |x| x.completed? || x.project_id != @project.id }.size
end
end
@ -88,11 +89,9 @@ class MobileController < ApplicationController
end
def init
@contexts = Context.find :all, :order => 'position ASC',
:conditions => ['user_id = ?', @user.id]
@projects = Project.find :all, :order => 'position ASC',
:conditions => ['user_id = ? and state = ?', @user.id, "active"]
@all_todos = Todo.find(:all, :conditions => ['user_id = ? and type = ?', @user.id, "Immediate"])
@contexts = @user.contexts.find(:all, :order => 'position ASC')
@projects = @user.projects.find_in_state(:all, :active, :order => 'position ASC')
@all_todos = @user.todos.find(:all, :conditions => ['state = ? or state =?', "active", "completed"])
end
end

View file

@ -17,6 +17,7 @@ class ProjectController < ApplicationController
#
def list
init
init_project_hidden_todo_counts
@page_title = "TRACKS::List Projects"
respond_to do |wants|
wants.html
@ -125,38 +126,27 @@ class ProjectController < ApplicationController
end
end
# Toggles the 'done' status of the action
#
def toggle_check
self.init
@item = check_user_return_item
@item.toggle!('done')
@item.completed = Time.now() # For some reason, the before_save in todo.rb stopped working
@saved = @item.save
if @saved
@down_count = Todo.find(:all, :conditions => ["todos.user_id = ? and todos.done = ? and todos.project_id IN (?)", @user.id, false, @item.project_id]).size.to_s
@done_count = Todo.find(:all, :conditions => ["todos.user_id = ? and todos.done = ? and todos.project_id IN (?)", @user.id, true, @item.project_id]).size.to_s
end
return if request.xhr?
if @saved
flash[:notice] = "The action <strong>'#{@item.description}'</strong> was marked as <strong>#{@item.done? ? 'complete' : 'incomplete' }</strong>"
else
flash[:notice] = "The action <strong>'#{@item.description}'</strong> was NOT marked as <strong>#{@item.done? ? 'complete' : 'incomplete' } due to an error on the server.</strong>"
end
redirect_to :action => "list"
end
# Edit the details of the project
#
def update
self.init
check_user_set_project
@project.transition_to(params['project']['state'])
params['project'].delete('state')
@project.attributes = params['project']
@project.name = deurlize(@project.name)
if @project.save
render :partial => 'project_listing', :object => @project
if params['wants_render']
if (@project.hidden?)
@project_project_hidden_todo_counts = Hash.new
@project_project_hidden_todo_counts[@project.id] = @project.reload().not_done_todo_count(:include_project_hidden_todos => true)
else
@project_not_done_counts[@project.id] = @project.reload().not_done_todo_count(:include_project_hidden_todos => true)
end
render :partial => 'project_listing', :object => @project
else
render :text => 'Success'
end
else
flash[:warning] = "Couldn't update project"
render :text => ''
@ -242,14 +232,14 @@ class ProjectController < ApplicationController
@projects = @user.projects
@contexts = @user.contexts
@todos = @user.todos
@done = Todo.find(:all, :conditions => ["todos.user_id = ? and todos.done = ?", @user.id, true], :include => [:project], :order => "completed DESC")
init_not_done_counts
@done = @user.todos.find_in_state(:all, :completed, :order => "completed_at DESC")
init_data_for_sidebar
end
def init_todos
check_user_set_project
@done = @project.done_todos
@not_done = @project.not_done_todos
@not_done = @project.not_done_todos(:include_project_hidden_todos => true)
@count = @not_done.size
end

View file

@ -26,8 +26,8 @@ class TodoController < ApplicationController
@done = nil
if max_completed > 0
@done = Todo.find(:all,
:conditions => ['todos.user_id = ? and todos.done = ?', @user.id, true],
:order => 'todos.completed DESC',
:conditions => ['todos.user_id = ? and todos.state = ?', @user.id, 'completed'],
:order => 'todos.completed_at DESC',
:limit => max_completed,
:include => [ :project, :context ])
end
@ -39,7 +39,7 @@ class TodoController < ApplicationController
end
# Set count badge to number of not-done, not hidden context items
@count = @todos.reject { |x| x.done? || x.context.hide? }.size
@count = @todos.reject { |x| !x.active? || x.context.hide? }.size
respond_to do |wants|
wants.html
@ -79,7 +79,7 @@ class TodoController < ApplicationController
wants.js do
if @saved
init_todos
@up_count = @todos.reject { |x| x.done? or x.context.hide? }.size.to_s
@up_count = @todos.reject { |x| !x.active? or x.context.hide? }.size.to_s
end
render :action => 'create'
end
@ -119,21 +119,20 @@ class TodoController < ApplicationController
#
def toggle_check
init
logger.info "source view is " + @source_view
@item = check_user_return_item
@item.toggle!('done')
@item.completed = Time.now() # For some reason, the before_save in todo.rb stopped working
@item.toggle_completion()
@saved = @item.save
@remaining_undone_in_context = @user.contexts.find(@item.context_id).not_done_todos.length
if @saved
@down_count = @todos.reject { |x| x.done? || x.context.hide? }.size.to_s
@remaining_undone_in_context = @user.contexts.find(@item.context_id).not_done_todo_count
determine_down_count
end
return if request.xhr?
if @saved
redirect_with_notice "The action <strong>'#{@item.description}'</strong> was marked as <strong>#{@item.done? ? 'complete' : 'incomplete' }</strong>", :action => "index"
redirect_with_notice "The action <strong>'#{@item.description}'</strong> was marked as <strong>#{@item.completed? ? 'complete' : 'incomplete' }</strong>", :action => "index"
else
redirect_with_notice "The action <strong>'#{@item.description}'</strong> was NOT marked as <strong>#{@item.done? ? 'complete' : 'incomplete' } due to an error on the server.</strong>", :action => "index"
redirect_with_notice "The action <strong>'#{@item.description}'</strong> was NOT marked as <strong>#{@item.completed? ? 'complete' : 'incomplete' } due to an error on the server.</strong>", :action => "index"
end
end
@ -207,7 +206,7 @@ class TodoController < ApplicationController
wants.js do
if @saved
@down_count = determine_down_count
determine_down_count
source_view do |from|
from.todo do
@remaining_undone_in_context = @user.contexts.find(@context_id).not_done_todos.length
@ -249,7 +248,7 @@ class TodoController < ApplicationController
private
def check_user_return_item
item = Todo.find( params['id'] )
item = Todo.find( params['id'].to_i )
if @user == item.user
return item
else
@ -267,39 +266,30 @@ class TodoController < ApplicationController
def init
@source_view = params['_source_view'] || 'todo'
@projects = @user.projects
@contexts = @user.contexts
init_todos
init_not_done_counts
init_data_for_sidebar
init_todos
end
def init_todos
# Exclude hidden projects from count on home page
@todos = Todo.find(:all,
:conditions => ['todos.user_id = ? and todos.type = ? and (projects.state != ? or todos.project_id is ?)', @user.id, "Immediate", "hidden", nil],
:include => [ :project, :context ])
@todos = @user.todos.find(:all, :conditions => ['todos.state = ? or todos.state = ?', 'active', 'complete'], :include => [ :project, :context ])
# Exclude hidden projects from the home page
@not_done_todos = Todo.find(:all,
:conditions => ['todos.user_id = ? and todos.type = ? and todos.done = ? and (projects.state != ? or todos.project_id is ?)', @user.id, "Immediate", false, "hidden", nil],
:order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC",
:include => [ :project, :context ])
@not_done_todos = @user.todos.find(:all, :conditions => ['todos.state = ?', 'active'], :order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC", :include => [ :project, :context ])
end
def determine_down_count
source_view do |from|
from.todo do
@down_count = Todo.count(:conditions => ['todos.user_id = ? and todos.type = ? and todos.done = ? and contexts.hide = ?',
@user.id, "Immediate", false, false],
:include => [ :context ])
@down_count = Todo.count_by_sql(['SELECT COUNT(*) FROM todos, contexts WHERE todos.context_id = contexts.id and todos.user_id = ? and todos.state = ? and contexts.hide = ?', @user.id, 'active', false])
end
from.context do
@down_count = Todo.count(:conditions => ['todos.user_id = ? and todos.type = ? and todos.done = ? and todos.context_id = ?',
@user.id, "Immediate", false, @context_id])
@down_count = @user.contexts.find(@item.context_id).todos.count_in_state(:active)
end
from.project do
@down_count = Todo.count(:conditions => ['todos.user_id = ? and todos.type = ? and todos.done = ? and todos.project_id = ?',
@user.id, "Immediate", false, @project_id]) unless @project_id == nil
unless @item.project_id == nil
@down_count = @user.projects.find(@item.project_id).todos.count_in_state(:active)
end
end
end
end

View file

@ -117,7 +117,11 @@ module ApplicationHelper
# actions or multiple actions
#
def count_undone_todos(todos_parent, string="actions")
count = eval "@#{todos_parent.class.to_s.downcase}_not_done_counts[#{todos_parent.id}]"
if (todos_parent.is_a?(Project) && todos_parent.hidden?)
count = eval "@project_project_hidden_todo_counts[#{todos_parent.id}]"
else
count = eval "@#{todos_parent.class.to_s.downcase}_not_done_counts[#{todos_parent.id}]"
end
count = 0 if count == nil
#count = todos_parent.todos.select{|t| !t.done }.size
if count == 1

View file

@ -11,8 +11,8 @@ module FeedHelper
if item.context_id == context.id
result_string << "\n" + context.name.upcase + ":\n" if result_string.empty?
if (item.done == 1 || item.done == true) && item.completed
result_string << " [Completed: " + format_date(item.completed) + "] "
if (item.completed?) && item.completed_at
result_string << " [Completed: " + format_date(item.completed_at) + "] "
end
if item.due

View file

@ -22,7 +22,7 @@ module TodoHelper
{ :url => url_options, :confirm => "Are you sure that you want to delete the action, \'#{item.description}\'?" },
{ :class => "icon" }
) + "\n"
if !item.done?
if !item.completed?
url_options[:action] = 'edit'
str << link_to_remote( image_tag_for_edit(item),
{ :url => url_options, :loading => visual_effect(:pulsate, "action-#{item.id}-edit-icon") },
@ -42,7 +42,7 @@ module TodoHelper
# * l3: created more than 3 x staleness_starts
#
def staleness_class(item)
if item.due || item.done?
if item.due || item.completed?
return ""
elsif item.created_at < (@user.preference.staleness_starts * 3).days.ago
return " stale_l3"

View file

@ -1,6 +1,6 @@
class Context < ActiveRecord::Base
has_many :todos, :dependent => true, :order => "completed DESC"
has_many :todos, :dependent => true, :order => "completed_at DESC"
belongs_to :user
acts_as_list :scope => :user

View file

@ -1,9 +0,0 @@
class Deferred < Todo
validates_presence_of :show_from
def validate
if show_from != nil && show_from < Date.today()
errors.add("Show From", "must be a date in the future.")
end
end
end

View file

@ -1,2 +0,0 @@
class Immediate < Todo
end

View file

@ -16,7 +16,7 @@ class Project < ActiveRecord::Base
acts_as_todo_container :find_todos_include => :context
state :active
state :hidden
state :hidden, :enter => :hide_todos, :exit => :unhide_todos
state :completed
event :activate do
@ -44,6 +44,33 @@ class Project < ActiveRecord::Base
def linkurl_present?
attribute_present?("linkurl")
end
def hide_todos
todos.each do |t|
t.hide! unless t.completed?
t.save
end
end
def unhide_todos
todos.each do |t|
t.unhide! if t.project_hidden?
t.save
end
end
def transition_to(candidate_state)
case candidate_state.to_sym
when current_state
return
when :hidden
hide!
when :active
activate!
when :completed
complete!
end
end
end

View file

@ -5,6 +5,34 @@ class Todo < ActiveRecord::Base
belongs_to :project
belongs_to :user
acts_as_state_machine :initial => :active, :column => 'state'
state :active, :enter => Proc.new { |t| t.show_from = nil }
state :project_hidden
state :completed, :enter => Proc.new { |t| t.completed_at = Time.now() }, :exit => Proc.new { |t| t.completed_at = nil }
state :deferred
event :defer do
transitions :to => :deferred, :from => [:active]
end
event :complete do
transitions :to => :completed, :from => [:active, :project_hidden, :deferred]
end
event :activate do
transitions :to => :active, :from => [:project_hidden, :completed, :deferred]
end
event :hide do
transitions :to => :project_hidden, :from => [:active, :deferred]
end
event :unhide do
transitions :to => :deferred, :from => [:project_hidden], :guard => Proc.new{|t| t.show_from != nil}
transitions :to => :active, :from => [:project_hidden]
end
attr_protected :user
# Description field can't be empty, and must be < 100 bytes
@ -13,6 +41,21 @@ class Todo < ActiveRecord::Base
validates_length_of :description, :maximum => 100
validates_length_of :notes, :maximum => 60000, :allow_nil => true
# validates_chronic_date :due, :allow_nil => true
validates_presence_of :show_from, :if => :deferred?
def validate
if deferred? && show_from != nil && show_from < Date.today()
errors.add("Show From", "must be a date in the future.")
end
end
def toggle_completion
if completed?
activate!
else
complete!
end
end
alias_method :original_project, :project
@ -26,16 +69,16 @@ class Todo < ActiveRecord::Base
def self.find_completed(user_id)
done = self.find(:all,
:conditions => ['todos.user_id = ? and todos.done = ? and todos.completed is not null', user_id, true],
:order => 'todos.completed DESC',
:conditions => ['todos.user_id = ? and todos.state = ? and todos.completed_at is not null', user_id, 'completed'],
:order => 'todos.completed_at DESC',
:include => [ :project, :context ])
def done.completed_within( date )
reject { |x| x.completed < date }
reject { |x| x.completed_at < date }
end
def done.completed_more_than( date )
reject { |x| x.completed > date }
reject { |x| x.completed_at > date }
end
done

View file

@ -3,7 +3,7 @@ require 'digest/sha1'
class User < ActiveRecord::Base
has_many :contexts, :order => "position ASC"
has_many :projects, :order => "position ASC"
has_many :todos, :order => "completed DESC, created_at DESC"
has_many :todos, :order => "completed_at DESC, created_at DESC"
has_many :notes, :order => "created_at DESC"
has_one :preference

View file

@ -1,6 +1,6 @@
<% item = show_items %>
<% if !item.done? %>
<% if !item.completed? %>
<div id="item-<%= item.id %>-container">
<%= form_remote_tag( :url => url_for( :controller => "context", :action => "toggle_check", :id => item.id ),
:html => { :id=> "checkbox-notdone-#{item.id}", :class => "inline-form" },
@ -82,7 +82,7 @@
</div>
<!-- end div.checkbox -->
<div class="description">
<span class="grey"><%= format_date( item.completed ) %></span>
<span class="grey"><%= format_date( item.completed_at ) %></span>
<%= sanitize(item.description) %>
<% if item.project_id %>
<%= link_to( "[P]", { :controller => "project", :action => "show", :name => urlize(item.project.name) }, :title => "View project: #{item.project.name}" ) %>

View file

@ -1,26 +0,0 @@
if @saved
page.remove "item-#{@item.id}-container"
if @item.done?
# Don't try to insert contents into a non-existent container!
unless @user.preference.hide_completed_actions?
page.insert_html :top, "completed", :partial => 'todo/item', :locals => { :parent_container_type => "completed" }
page.visual_effect :highlight, "item-#{@item.id}", {'startcolor' => "'#99ff99'"}
if @down_count == '0'
page["c#{@item.context_id}empty-nd"].show
end
page.hide "empty-d" # If we've checked something as done, completed items can't be empty
end
else
page.call "todoItems.ensureVisibleWithEffectAppear", "c#{@item.context_id}"
page.insert_html :bottom, "c#{@item.context_id}", :partial => 'todo/item', :locals => { :parent_container_type => "context" }
page.visual_effect :highlight, "item-#{@item.id}", {'startcolor' => "'#99ff99'"}
if @done_count == '0'
page.show "empty-d"
end
page["c#{@item.context_id}empty-nd"].hide # If we've checked something as undone, uncompleted items can't be empty
end
page.hide "status"
page.replace_html "badge_count", @down_count
else
page.replace_html "status", content_tag("div", content_tag("h2", "#{pluralize(@item.errors.count, "error")} prohibited this record from being saved") + content_tag("p", "There were problems with the following fields:") + content_tag("ul", @item.errors.each_full { |msg| content_tag("li", msg) }), "id" => "ErrorExplanation", "class" => "ErrorExplanation")
end

View file

@ -11,7 +11,7 @@ xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do
due = "<div>Due: #{format_date(i.due)}</div>\n" if i.due?
toggle_link = link_to( "mark as done", {:only_path => false, :controller => "todo", :action => "toggle_check", :id => i.id})
done = "<div>#{toggle_link}</div>" unless i.completed?
done = "<div>Completed: #{format_date(i.completed)}</div>\n" if i.completed?
done = "<div>Completed: #{format_date(i.completed_at)}</div>\n" if i.completed?
context_link = link_to( i.context.name, { :only_path => false, :controller => "context", :action => "show", :name => urlize(i.context.name) } )
if i.project_id?
project_link = link_to (i.project.name, { :only_path => false, :controller => "project", :action => "show", :name => urlize(i.project.name)} )

View file

@ -2,7 +2,7 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<% if @prefs["refresh"].to_i != 0 -%>
<% if @prefs.refresh != 0 -%>
<meta http-equiv="Refresh" content="<%= @prefs["refresh"].to_i*60 %>;url=<%= request.request_uri %>">
<% end -%>
<%= stylesheet_link_tag "standard" %>

View file

@ -34,7 +34,7 @@
<table style="table-layout: fixed;" width="450">
<%= render :partial => 'project_form', :object => project %>
<tr>
<td width="150">&nbsp;</td>
<td width="150">&nbsp; <input type="hidden" name="wants_render" value="false" /></td>
<td width="300"><input type="submit" value="Update" />&nbsp;<a href="javascript:void(0);" onclick="Element.toggle('project-<%= project.id %>','project-<%= project.id %>-edit-form');Form.reset('form-project-<%= project.id %>');">Cancel</a></td>
</tr>
</table>

View file

@ -1,5 +1,5 @@
<% item = show_items %>
<% if !item.done? %>
<% if !item.completed? %>
<div id="item-<%= item.id %>-container">
<%= form_remote_tag( :url => url_for( :controller => "project", :action => "toggle_check", :id => item.id ),
:html => { :id=> "checkbox-notdone-#{item.id}", :class => "inline-form" },
@ -82,7 +82,7 @@
</div>
<!-- end div.checkbox -->
<div class="description">
<span class="grey"><%= format_date( item.completed ) %></span>
<span class="grey"><%= format_date( item.completed_at ) %></span>
<%= sanitize(item.description) %>
<% if item.project_id %>
<%= link_to( "[C]", { :controller => "context", :action => "show", :name => urlize(item.context.name) }, :title => "View context: #{item.context.name}" ) %>

View file

@ -38,7 +38,7 @@
<h2>Status</h2>
<div>
<% ['active', 'hidden', 'completed'].each do | state | %>
<% js = "new Ajax.Request('#{ url_for :controller => 'project', :action => 'update', :id => @project.id, 'project[state]' => state }', {asynchronous:true, evalScripts:true});" %>
<% js = "new Ajax.Request('#{ url_for :controller => 'project', :action => 'update', :id => @project.id, :wants_render => false, 'project[state]' => state }', {asynchronous:true, evalScripts:true});" %>
<%= radio_button(:project, 'state', state, {:class => 'project-done', :onclick => js}) %> <%= state.titlecase %>
<% end %>
</div>

View file

@ -1,26 +0,0 @@
if @saved
page.remove "item-#{@item.id}-container"
if @item.done?
# Don't try to insert contents into a non-existent container!
unless @user.preference.hide_completed_actions?
page.insert_html :top, "completed", :partial => 'todo/item', :locals => { :parent_container_type => "project" }
page.visual_effect :highlight, "item-#{@item.id}", {'startcolor' => "'#99ff99'"}
if @down_count == '0'
page["p#{@item.project_id}empty-nd"].show
end
page.hide "empty-d" # If we've checked something as done, completed items can't be empty
end
else
page.call "todoItems.ensureVisibleWithEffectAppear", "p#{@item.project_id}"
page.insert_html :bottom, "p#{@item.project_id}", :partial => 'todo/item', :locals => { :parent_container_type => "project" }
page.visual_effect :highlight, "item-#{@item.id}", {'startcolor' => "'#99ff99'"}
if @done_count == '0'
page.show "empty-d"
end
page["p#{@item.project_id}empty-nd"].hide # If we've checked something as undone, uncompleted items can't be empty
end
page.hide "status"
page.replace_html "badge_count", @down_count
else
page.replace_html "status", content_tag("div", content_tag("h2", "#{pluralize(@item.errors.count, "error")} prohibited this record from being saved") + content_tag("p", "There were problems with the following fields:") + content_tag("ul", @item.errors.each_full { |msg| content_tag("li", msg) }), "id" => "ErrorExplanation", "class" => "ErrorExplanation")
end

View file

@ -6,13 +6,14 @@
<% end -%>
</ul>
<% if @user.preference.show_hidden_projects_in_sidebar %>
<h3>Hidden Projects:</h3>
<ul>
<% for project in @projects.select{|p| p.hidden? } -%>
<li id="sidebar-project-<%= project.id %>" class="sidebar-project"><%= link_to( sanitize(project.name), { :controller => "project", :action => "show", :name => urlize(project.name) } ) + " (" + count_undone_todos(project,"actions") + ")" %></li>
<% end -%>
</ul>
<% end %>
<% if @user.preference.show_completed_projects_in_sidebar %>
<h3>Completed Projects:</h3>

View file

@ -1,7 +1,7 @@
<tr>
<% if done.completed %>
<% if done.completed_at %>
<td valign="top"><%= image_tag( "done", :width=>"16", :height=>"16", :border=>"0") %></td>
<td valign="top"><span class="grey"><%= format_date( done.completed ) %></span></td>
<td valign="top"><span class="grey"><%= format_date( done.completed_at ) %></span></td>
<td valign="top"><%= " " + sanitize(done.description) + " "%>
<% if done.project_id %>

View file

@ -46,13 +46,13 @@
<td class="label"><label for="item_due">Due</td>
<td><input name="item[due]" id="due_<%= @item.id %>" type="text" value="<%= format_date(@item.due) %>" tabindex="5" size="10" onFocus="Calendar.setup" /></td>
</tr>
<% if @item.class == Deferred -%>
<% if @item.current_state == :deferred -%>
<tr>
<td class="label"><label for="item_show_from">Show from</td>
<td><input name="item[show_from]" id="show_from_<%= @item.id %>" type="text" value="<%= format_date(@item.show_from) %>" tabindex="5" size="10" onFocus="Calendar.setup" /></td>
</tr>
<% end -%>
<% if controller.controller_name == "project" || @item.class == 'Deferred' -%>
<% if controller.controller_name == "project" || @item.current_state == :deferred -%>
<input type="hidden" name="on_project_page" value="true" />
<% end -%>
<tr>
@ -62,6 +62,6 @@
</table>
<%= calendar_setup( "due_#{@item.id}" ) %>
<% if @item.class == Deferred -%>
<% if @item.current_state == :deferred -%>
<%= calendar_setup( "show_from_#{@item.id}" ) %>
<% end -%>

View file

@ -3,17 +3,17 @@
<div id="item-<%= item.id %>">
<%= link_to_remote_todo item %>
<input type="checkbox" class="item-checkbox"
onclick="new Ajax.Request('<%= url_for :controller => controller.controller_name, :action => 'toggle_check', :id => item.id %>', {asynchronous:true, evalScripts:true});"
name="item_id" value="<%= item.id %>"<% if item.done? %> checked="checked" <% end %> />
onclick="new Ajax.Request('<%= url_for :controller => 'todo', :action => 'toggle_check', :id => item.id, :_source_view => @source_view %>', {asynchronous:true, evalScripts:true});"
name="item_id" value="<%= item.id %>"<% if item.completed? %> checked="checked" <% end %> />
<div class="description<%= staleness_class( item ) %>"><% # start of div which has a class 'description', and possibly 'stale_11', 'stale_12', 'stale_13' etc %>
<% if item.done? -%>
<span class="grey"><%= format_date( item.completed ) %></span>
<% if item.completed? -%>
<span class="grey"><%= format_date( item.completed_at ) %></span>
<% else -%>
<%= due_date( item.due ) -%>
<% end -%>
<%= sanitize(item.description) %>
<% if item.done? -%>
<% if item.completed? -%>
(<%= item.context.name %><%= ", " + item.project.name if item.project_id %>)
<% else -%>
<% if parent_container_type == "project" -%>

View file

@ -1,21 +1,33 @@
if @saved
item_container_id = "c#{@item.context_id}"
parent_container_type = "context"
if source_view_is :context
empty_container_msg_div_id = "c#{@item.context_id}empty-nd"
elsif source_view_is :project
item_container_id = "p#{@item.project_id}"
empty_container_msg_div_id = "p#{@item.project_id}empty-nd"
parent_container_type = "project"
end
page.remove "item-#{@item.id}-container"
# For some reason, the code below works on the project and context pages, but
# not on the todo/list page when the item being deleted has just been added
# page.call "fadeAndRemoveItem", "item-#{@item.id}-container"
if @item.done?
if @item.completed?
# Don't try to insert contents into a non-existent container!
unless @user.preference.hide_completed_actions?
page.insert_html :top, "completed", :partial => 'todo/item', :locals => { :parent_container_type => "completed" }
page.visual_effect :highlight, "item-#{@item.id}", {'startcolor' => "'#99ff99'"}
page[empty_container_msg_div_id].show if @down_count == '0' && !empty_container_msg_div_id.nil?
page.hide "empty-d" # If we've checked something as done, completed items can't be empty
end
if @remaining_undone_in_context == 0
page.visual_effect :fade, "c#{@item.context_id}", :duration => 0.4
if @remaining_undone_in_context == 0 && source_view_is(:todo)
page.visual_effect :fade, item_container_id, :duration => 0.4
end
else
page.call "todoItems.ensureVisibleWithEffectAppear", "c#{@item.context_id}"
page.insert_html :bottom, "c#{@item.context_id}", :partial => 'todo/item', :locals => { :parent_container_type => "context" }
page.call "todoItems.ensureVisibleWithEffectAppear", item_container_id
page.insert_html :bottom, item_container_id, :partial => 'todo/item', :locals => { :parent_container_type => parent_container_type }
page.visual_effect :highlight, "item-#{@item.id}", {'startcolor' => "'#99ff99'"}
page.show "empty-d" if @done_count == '0'
page[empty_container_msg_div_id].hide unless empty_container_msg_div_id.nil? # If we've checked something as undone, uncompleted items can't be empty
end
page.hide "status"
page.replace_html "badge_count", @down_count

View file

@ -50,6 +50,7 @@
<%= row_with_select_field("week_starts", Preference.day_number_to_name_map.invert.sort{|a,b| a[1]<=>b[1]})%>
<%= row_with_select_field("due_style", [['Due in ___ days',0],['Due on _______',1]]) %>
<%= row_with_select_field("show_completed_projects_in_sidebar") %>
<%= row_with_select_field("show_hidden_projects_in_sidebar") %>
<%= row_with_select_field("show_hidden_contexts_in_sidebar") %>

View file

@ -11,6 +11,7 @@
<li>Week starts on: <span class="highlight"><%= Preference.day_number_to_name_map[@prefs.week_starts] %></span></li>
<li>Show the last <span class="highlight"><%= @prefs.show_number_completed %></span> completed items on the home page</li>
<li>Show completed projects in sidebar: <span class="highlight"><%= @prefs.show_completed_projects_in_sidebar %></span></li>
<li>Show hidden projects in sidebar: <span class="highlight"><%= @prefs.show_hidden_projects_in_sidebar %></span></li>
<li>Show hidden contexts in sidebar: <span class="highlight"><%= @prefs.show_hidden_contexts_in_sidebar %></span></li>
<li>Staleness starts after <span class="highlight"><%= @prefs.staleness_starts %></span> days</li>
<li>Due style: <span class="highlight">

View file

@ -0,0 +1,44 @@
class ConvertTodoToStateMachine < ActiveRecord::Migration
class Todo < ActiveRecord::Base; belongs_to :project; end
class Immediate < Todo; end
class Deferred < Todo; end
class Project < ActiveRecord::Base; end
def self.up
ActiveRecord::Base.transaction do
add_column :todos, :state, :string, :limit => 20, :default => "immediate", :null => false
@todos = Todo.find(:all)
@todos.each do |todo|
if todo.done?
todo.state = 'completed'
elsif todo.type == 'Deferred'
todo.state = 'deferred'
elsif !todo.project.nil? && todo.project.state == 'hidden'
todo.state = 'project_hidden'
else
todo.state = 'active'
end
todo.save
end
rename_column :todos, :completed, :completed_at
remove_column :todos, :done
remove_column :todos, :type
end
end
def self.down
ActiveRecord::Base.transaction do
add_column :todos, :done, :integer, :limit => 4, :default => 0, :null => false
add_column :todos, :type, :string, :default => "Immediate", :null => false
rename_column :todos, :completed_at, :completed
@todos = Todo.find(:all)
@todos.each do |todo|
todo.done = todo.state == 'completed'
todo.type = todo.type == 'deferred' ? 'Deferred' : 'Immediate'
todo.save
end
remove_column :todos, :state
end
end
end

View file

@ -0,0 +1,13 @@
class PrefToShowHiddenProjectsInSidebar < ActiveRecord::Migration
class Preferences < ActiveRecord::Base; end
def self.up
add_column :preferences, :show_hidden_projects_in_sidebar, :boolean, :default => true, :null => false
end
def self.down
remove_column :preferences, :show_hidden_projects_in_sidebar
end
end

View file

@ -2,13 +2,13 @@
# migrations feature of ActiveRecord to incrementally modify your database, and
# then regenerate this schema definition.
ActiveRecord::Schema.define(:version => 18) do
ActiveRecord::Schema.define(:version => 20) do
create_table "contexts", :force => true do |t|
t.column "name", :string, :default => "", :null => false
t.column "hide", :integer, :limit => 4, :default => 0, :null => false
t.column "position", :integer, :default => 0, :null => false
t.column "hide", :boolean, :default => false
t.column "user_id", :integer, :default => 1
t.column "user_id", :integer, :default => 0, :null => false
end
create_table "notes", :force => true do |t|
@ -50,12 +50,13 @@ ActiveRecord::Schema.define(:version => 18) do
t.column "admin_email", :string, :default => "butshesagirl@rousette.org.uk", :null => false
t.column "refresh", :integer, :default => 0, :null => false
t.column "verbose_action_descriptors", :boolean, :default => false, :null => false
t.column "show_hidden_projects_in_sidebar", :boolean, :default => true, :null => false
end
create_table "projects", :force => true do |t|
t.column "name", :string, :default => "", :null => false
t.column "position", :integer, :default => 0, :null => false
t.column "user_id", :integer, :default => 1
t.column "user_id", :integer, :default => 0, :null => false
t.column "description", :text
t.column "state", :string, :limit => 20, :default => "active", :null => false
end
@ -70,23 +71,22 @@ ActiveRecord::Schema.define(:version => 18) do
create_table "todos", :force => true do |t|
t.column "context_id", :integer, :default => 0, :null => false
t.column "project_id", :integer
t.column "description", :string, :default => "", :null => false
t.column "description", :string, :limit => 100, :default => "", :null => false
t.column "notes", :text
t.column "done", :boolean, :default => false, :null => false
t.column "created_at", :datetime
t.column "due", :date
t.column "completed", :datetime
t.column "user_id", :integer, :default => 1
t.column "type", :string, :default => "Immediate", :null => false
t.column "completed_at", :datetime
t.column "project_id", :integer
t.column "user_id", :integer, :default => 0, :null => false
t.column "show_from", :date
t.column "state", :string, :limit => 20, :default => "immediate", :null => false
end
create_table "users", :force => true do |t|
t.column "login", :string, :limit => 80, :default => "", :null => false
t.column "password", :string, :limit => 40, :default => "", :null => false
t.column "login", :string, :limit => 80
t.column "password", :string, :limit => 40
t.column "word", :string
t.column "is_admin", :boolean, :default => false, :null => false
t.column "is_admin", :integer, :limit => 4, :default => 0, :null => false
t.column "first_name", :string
t.column "last_name", :string
t.column "auth_type", :string, :default => "database", :null => false

View file

@ -23,8 +23,8 @@ module Tracks
module InstanceMethods
def not_done_todos
@not_done_todos = self.find_not_done_todos if @not_done_todos == nil
def not_done_todos(opts={})
@not_done_todos = self.find_not_done_todos(opts) if @not_done_todos == nil
@not_done_todos
end
@ -33,19 +33,41 @@ module Tracks
@done_todos
end
def find_not_done_todos
def find_not_done_todos(opts={})
where_state_sql = "todos.state = 'active'"
if opts.has_key?(:include_project_hidden_todos) && (opts[:include_project_hidden_todos] == true)
where_state_sql = "(todos.state = 'active' or todos.state = 'project_hidden')"
end
todos = Todo.find(:all,
:conditions => ["todos.#{self.class.base_class.name.singularize.downcase}_id = ? and todos.type = ? and todos.done = ?", id, "Immediate", false],
:conditions => ["todos.#{self.class.base_class.name.singularize.downcase}_id = ? and #{where_state_sql}", id],
:order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC",
:include => find_todos_include)
end
def find_done_todos
todos = Todo.find :all, :conditions => ["todos.#{self.class.base_class.name.singularize.downcase}_id = ? AND todos.type = ? AND todos.done = ?", id, "Immediate", true],
:order => "completed DESC",
todos = Todo.find(:all, :conditions => ["todos.#{self.class.base_class.name.singularize.downcase}_id = ? AND todos.state = ?", id, "completed"],
:order => "completed_at DESC",
:include => find_todos_include,
:limit => @user.preference.show_number_completed
:limit => @user.preference.show_number_completed)
end
def not_done_todo_count(opts={})
where_state_sql = "todos.state = 'active'"
if opts.has_key?(:include_project_hidden_todos) && (opts[:include_project_hidden_todos] == true)
where_state_sql = "(todos.state = 'active' or todos.state = 'project_hidden')"
end
Todo.count(:conditions => ["todos.#{self.class.base_class.name.singularize.downcase}_id = ? and #{where_state_sql}", id],
:order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC",
:include => find_todos_include)
end
def done_todo_count
Todo.count(:conditions => ["todos.#{self.class.base_class.name.singularize.downcase}_id = ? AND todos.state = ?", id, "completed"],
:order => "completed_at DESC",
:include => find_todos_include,
:limit => @user.preference.show_number_completed)
end
end

View file

@ -7,6 +7,7 @@ admin_user_prefs:
show_number_completed: 5
show_completed_projects_in_sidebar: true
show_hidden_contexts_in_sidebar: true
show_hidden_projects_in_sidebar: true
admin_email: butshesagirl@rousette.org.uk
week_starts: 1
due_style: 0
@ -21,6 +22,7 @@ other_user_prefs:
show_number_completed: 5
show_completed_projects_in_sidebar: true
show_hidden_contexts_in_sidebar: true
show_hidden_projects_in_sidebar: true
admin_email: butshesagirl@rousette.org.uk
week_starts: 1
due_style: 0

View file

@ -25,10 +25,10 @@ end
project_id: 2
description: Call Bill Gates to find out how much he makes per day
notes: ~
done: false
state: active
created_at: <%= last_week %>
due: <%= two_weeks_hence %>
completed: ~
completed_at: ~
user_id: 1
2:
@ -37,10 +37,10 @@ end
project_id: 3
description: Call dinosaur exterminator
notes: Ask him if I need to hire a skip for the corpses.
done: false
state: active
created_at: <%= today %>
due: <%= two_weeks_hence %>
completed: ~
completed_at: ~
user_id: 1
3:
@ -49,10 +49,10 @@ end
project_id: ~
description: Buy milk
notes: ~
done: 1
state: completed
created_at: <%= today %>
due: ~
completed: <%= today %>
completed_at: <%= today %>
user_id: 1
4:
@ -61,10 +61,10 @@ end
project_id: ~
description: Buy bread
notes: ~
done: 1
state: completed
created_at: <%= today %>
due: ~
completed: <%= today %>
completed_at: <%= today %>
user_id: 1
5:
@ -73,10 +73,10 @@ end
project_id: 1
description: Construct time dilation device
notes: ~
done: false
state: active
created_at: <%= today %>
due: ~
completed: ~
completed_at: ~
user_id: 1
6:
@ -85,10 +85,10 @@ end
project_id: 1
description: Phone Grandfather to ask about the paradox
notes: Added some _notes_.
done: false
state: active
created_at: <%= today %>
due: <%= last_week %>
completed: ~
completed_at: ~
user_id: 1
7:
@ -97,10 +97,10 @@ end
project_id: 3
description: Get a book out of the library
notes: 'Dinosaurs''R'
done: false
state: active
created_at: <%= today %>
due: ~
completed: ~
completed_at: ~
user_id: 1
8:
@ -109,10 +109,10 @@ end
project_id: ~
description: Upgrade to Rails 0.9.1
notes: ~
done: true
state: completed
created_at: <%= today %>
due: <%= today %>
completed: <%= today %>
completed_at: <%= today %>
user_id: 1
9:
@ -121,10 +121,10 @@ end
project_id: ~
description: This should be due today
notes: ~
done: false
state: active
created_at: <%= today %>
due: <%= today %>
completed: ~
completed_at: ~
user_id: 1
10:
@ -133,10 +133,10 @@ end
project_id: ~
description: foo
notes: ~
done: 1
state: completed
created_at: <%= today %>
due: <%= two_weeks_hence %>
completed: <%= today %>
completed_at: <%= today %>
user_id: 1
11:
@ -145,10 +145,10 @@ end
project_id: 2
description: Buy shares
notes: ~
done: false
state: active
created_at: <%= today %>
due: <%= next_week %>
completed: ~
completed_at: ~
user_id: 1
12:
@ -157,10 +157,10 @@ end
project_id: 3
description: Buy stegosaurus bait
notes: ~
done: false
state: active
created_at: <%= today %>
due: <%= next_week %>
completed: ~
completed_at: ~
user_id: 1
13:
@ -169,10 +169,10 @@ end
project_id: 3
description: New action in context
notes: Some notes
done: false
state: active
created_at: <%= today %>
due: <%= next_week %>
completed: ~
completed_at: ~
user_id: 1
14:
@ -181,8 +181,8 @@ end
project_id: 2
description: Call stock broker
notes: 'tel: 12345'
done: false
state: active
created_at: <%= last_week %>
due: ~
completed: ~
completed_at: ~
user_id: 1

View file

@ -6,12 +6,17 @@ require 'context_controller'
class ContextController; def rescue_action(e) raise e end; end
class ContextControllerTest < TodoContainerControllerTestBase
fixtures :users, :contexts
fixtures :users, :preferences, :contexts
def setup
perform_setup(Context, ContextController)
end
def test_contexts_list
@request.session['user_id'] = users(:admin_user).id
get :list
end
def test_create_context_via_ajax_increments_number_of_context
assert_ajax_create_increments_count '@newcontext'
end

View file

@ -6,11 +6,16 @@ require 'project_controller'
class ProjectController; def rescue_action(e) raise e end; end
class ProjectControllerTest < TodoContainerControllerTestBase
fixtures :users, :projects
fixtures :users, :todos, :preferences, :projects
def setup
perform_setup(Project, ProjectController)
end
def test_projects_list
@request.session['user_id'] = users(:admin_user).id
get :list
end
def test_create_project_via_ajax_increments_number_of_projects
assert_ajax_create_increments_count 'My New Project'
@ -37,4 +42,28 @@ class ProjectControllerTest < TodoContainerControllerTestBase
assert_rjs :visual_effect, :appear, "warning", :duration => '0.5'
end
def test_todo_state_is_project_hidden_after_hiding_project
p = projects(:timemachine)
todos = p.todos.find_in_state(:all, :active)
@request.session['user_id'] = users(:admin_user).id
xhr :post, :update, :id => 1, "project"=>{"name"=>p.name, "description"=>p.description, "state"=>"hidden"}
todos.each do |t|
assert_equal :project_hidden, t.reload().current_state
end
assert p.reload().hidden?
end
def test_not_done_counts_after_hiding_and_unhiding_project
p = projects(:timemachine)
todos = p.todos.find_in_state(:all, :active)
@request.session['user_id'] = users(:admin_user).id
xhr :post, :update, :id => 1, "project"=>{"name"=>p.name, "description"=>p.description, "state"=>"hidden"}
xhr :post, :update, :id => 1, "project"=>{"name"=>p.name, "description"=>p.description, "state"=>"active"}
todos.each do |t|
assert_equal :active, t.reload().current_state
end
assert p.reload().active?
end
end

View file

@ -12,13 +12,11 @@ class TodoControllerTest < Test::Unit::TestCase
@request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
end
# Test whether unlogged in users are redirected to login
#
def test_get_index
def test_get_index_when_not_logged_in
get :index
assert_redirected_to :controller => 'login', :action => 'login'
end
def test_not_done_counts
@request.session['user_id'] = users(:admin_user).id
get :index
@ -26,7 +24,7 @@ class TodoControllerTest < Test::Unit::TestCase
assert_equal 3, assigns['context_not_done_counts'][contexts(:call).id]
assert_equal 1, assigns['context_not_done_counts'][contexts(:lab).id]
end
def test_not_done_counts_after_hiding_project
p = Project.find(1)
p.hide!
@ -37,5 +35,36 @@ class TodoControllerTest < Test::Unit::TestCase
assert_equal 2, assigns['context_not_done_counts'][contexts(:call).id]
assert_equal nil, assigns['context_not_done_counts'][contexts(:lab).id]
end
def test_not_done_counts_after_hiding_and_unhiding_project
p = Project.find(1)
p.hide!
p.save!
p.activate!
p.save!
@request.session['user_id'] = users(:admin_user).id
get :index
assert_equal 2, assigns['project_not_done_counts'][projects(:timemachine).id]
assert_equal 3, assigns['context_not_done_counts'][contexts(:call).id]
assert_equal 1, assigns['context_not_done_counts'][contexts(:lab).id]
end
def test_destroy_item
@request.session['user_id'] = users(:admin_user).id
xhr :post, :destroy, :id => 1, :_source_view => 'todo'
assert_rjs :page, "item-1-container", :remove
#assert_rjs :replace_html, "badge-count", '9'
end
def test_update_item
t = Todo.find(1)
@request.session['user_id'] = users(:admin_user).id
xhr :post, :update, :id => 1, :_source_view => 'todo', "item"=>{"context_id"=>"1", "project_id"=>"2", "id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"11/30/2006"}
#assert_rjs :page, "item-1-container", :visual_effect, :highlight, :duration => '1'
t = Todo.find(1)
assert_equal "Call Warren Buffet to find out how much he makes per day", t.description
assert_equal Date.parse("11/30/2006"), t.due
end
end

View file

@ -37,9 +37,6 @@ class UserControllerTest < Test::Unit::TestCase
assert_template 'user/preference_edit_form'
end
# Test updating of preferences
# FIXME seems to be difficult to test serialization of preferences using YAML
#
def test_update_preferences
@request.session['user_id'] = users(:admin_user).id # log in the admin user
post :update_preferences, {:user => { :first_name => 'Jane', :last_name => 'Doe'}, :prefs => { :date_format => "%m-%d-%Y", :week_starts => "0", :show_number_completed => "10", :show_completed_projects_in_sidebar => "false", :show_hidden_contexts_in_sidebar => "false", :staleness_starts => "14", :due_style => "1", :admin_email => "my.email@domain.com" }}

View file

@ -18,17 +18,17 @@ class TodoTest < Test::Unit::TestCase
assert_equal 2, @not_completed1.project_id
assert_equal "Call Bill Gates to find out how much he makes per day", @not_completed1.description
assert_nil @not_completed1.notes
assert @not_completed1.done == false || @not_completed1.done == 0
assert @not_completed1.completed? == false
assert_equal 1.week.ago.strftime("%Y-%m-%d %H:%M"), @not_completed1.created_at.strftime("%Y-%m-%d %H:%M")
assert_equal 2.week.from_now.strftime("%Y-%m-%d"), @not_completed1.due.strftime("%Y-%m-%d")
assert_nil @not_completed1.completed
assert_nil @not_completed1.completed_at
assert_equal 1, @not_completed1.user_id
end
def test_completed
assert_kind_of Todo, @completed
assert @completed.done
assert_not_nil @completed.completed
assert @completed.completed?
assert_not_nil @completed.completed_at
end
# Validation tests

View file

@ -19,4 +19,10 @@ Usage:
assert_rjs :show, "post_1", "post_2", "post_3"
assert_rjs :sortable, 'sortable_item'
assert_rjs :toggle, "post_1", "post_2", "post_3"
assert_rjs :visual_effect, :highlight, "posts", :duration => '1.0'
assert_rjs :visual_effect, :highlight, "posts", :duration => '1.0'
For the square bracket syntax (page['some_id'].toggle) use :page followed by the id and then subsequent method calls. Assignment requires a '=' at the end of the method name followed by the value.
assert_rjs :page, 'some_id', :toggle
assert_rjs :page, 'some_id', :style, :color=, 'red'

View file

@ -2,6 +2,6 @@ author: Kevin Clark
summary: RJS Assertion Plugin
homepage: http://glu.ttono.us
plugin:
version: 0.1
version: 0.5
license: MIT
rails_version: 1.1.2+

View file

@ -1,2 +1,3 @@
# Give testing some culture
Test::Unit::TestCase.send :include, Arts
require 'test/unit/testcase'
Test::Unit::TestCase.send :include, Arts

View file

@ -85,8 +85,31 @@ module Arts
end
end
# To deal with [] syntax. I hate JavaScriptProxy so.. SO very much
def assert_rjs_page(*args)
content = build_method_chain!(args)
assert_match Regexp.new(Regexp.escape(content)), @response.body,
"Content did not include:\n #{content.to_s}"
end
protected
def build_method_chain!(args)
content = create_generator.send(:[], args.shift) # start $('some_id')....
while !args.empty?
if (method = args.shift.to_s) =~ /(.*)=$/
content = content.__send__(method, args.shift)
break
else
content = content.__send__(method)
content = content.__send__(:function_chain).first if args.empty?
end
end
content
end
def lined_response
@response.body.split("\n")
end
@ -107,4 +130,4 @@ module Arts
return create_generator.send(:arguments_for_call, args)
end
end
end
end

View file

@ -102,6 +102,18 @@ class ArtsController < ActionController::Base
end
end
def page_with_one_chained_method
render :update do |page|
page['some_id'].toggle
end
end
def page_with_assignment
render :update do |page|
page['some_id'].style.color = 'red'
end
end
def rescue_errors(e) raise e end
end
@ -358,4 +370,33 @@ class ArtsTest < Test::Unit::TestCase
assert_no_rjs :visual_effect, :highlight, "posts", :duration => '1.0'
end
end
# [] support
def test_page_with_one_chained_method
get :page_with_one_chained_method
assert_nothing_raised do
assert_rjs :page, 'some_id', :toggle
assert_no_rjs :page, 'some_other_id', :toggle
end
assert_raises(Test::Unit::AssertionFailedError) do
assert_rjs :page, 'some_other_id', :toggle
assert_no_rjs :page, 'some_id', :toggle
end
end
def test_page_with_assignment
get :page_with_assignment
assert_nothing_raised do
assert_rjs :page, 'some_id', :style, :color=, 'red'
assert_no_rjs :page, 'some_id', :color=, 'red'
end
assert_raises(Test::Unit::AssertionFailedError) do
assert_no_rjs :page, 'some_id', :style, :color=, 'red'
assert_rjs :page, 'some_other_id', :style, :color=, 'red'
end
end
end