Merge branch 'master' of git://github.com/bsag/tracks

This commit is contained in:
Reinier Balt 2008-12-23 12:11:40 +01:00
commit 1d0ed34cea
41 changed files with 3620 additions and 3331 deletions

View file

@ -1,268 +1,268 @@
# The filters added to this controller will be run for all controllers in the # The filters added to this controller will be run for all controllers in the
# application. Likewise will all the methods added be available for all # application. Likewise will all the methods added be available for all
# controllers. # controllers.
require_dependency "login_system" require_dependency "login_system"
require_dependency "tracks/source_view" require_dependency "tracks/source_view"
require "redcloth" require "redcloth"
require 'date' require 'date'
require 'time' require 'time'
# Commented the following line because of #744. It prevented rake db:migrate to # Commented the following line because of #744. It prevented rake db:migrate to
# run because this tag went looking for the taggings table that did not exist # run because this tag went looking for the taggings table that did not exist
# when you feshly create a new database Old comment: We need this in development # when you feshly create a new database Old comment: We need this in development
# mode, or you get 'method missing' errors # mode, or you get 'method missing' errors
# #
# Tag # Tag
class CannotAccessContext < RuntimeError; end class CannotAccessContext < RuntimeError; end
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
protect_from_forgery :secret => SALT protect_from_forgery :secret => SALT
helper :application helper :application
include LoginSystem include LoginSystem
helper_method :current_user, :prefs helper_method :current_user, :prefs
layout proc{ |controller| controller.mobile? ? "mobile" : "standard" } layout proc{ |controller| controller.mobile? ? "mobile" : "standard" }
before_filter :set_session_expiration before_filter :set_session_expiration
before_filter :set_time_zone before_filter :set_time_zone
prepend_before_filter :login_required prepend_before_filter :login_required
prepend_before_filter :enable_mobile_content_negotiation prepend_before_filter :enable_mobile_content_negotiation
after_filter :set_charset after_filter :set_charset
include ActionView::Helpers::TextHelper include ActionView::Helpers::TextHelper
include ActionView::Helpers::SanitizeHelper include ActionView::Helpers::SanitizeHelper
extend ActionView::Helpers::SanitizeHelper::ClassMethods extend ActionView::Helpers::SanitizeHelper::ClassMethods
helper_method :format_date, :markdown helper_method :format_date, :markdown
# By default, sets the charset to UTF-8 if it isn't already set # By default, sets the charset to UTF-8 if it isn't already set
def set_charset def set_charset
headers["Content-Type"] ||= "text/html; charset=UTF-8" headers["Content-Type"] ||= "text/html; charset=UTF-8"
end end
def set_session_expiration def set_session_expiration
# http://wiki.rubyonrails.com/rails/show/HowtoChangeSessionOptions # http://wiki.rubyonrails.com/rails/show/HowtoChangeSessionOptions
unless session == nil unless session == nil
return if @controller_name == 'feed' or session['noexpiry'] == "on" return if @controller_name == 'feed' or session['noexpiry'] == "on"
# If the method is called by the feed controller (which we don't have # If the method is called by the feed controller (which we don't have
# under session control) or if we checked the box to keep logged in on # under session control) or if we checked the box to keep logged in on
# login don't set the session expiry time. # login don't set the session expiry time.
if session if session
# Get expiry time (allow ten seconds window for the case where we have # Get expiry time (allow ten seconds window for the case where we have
# none) # none)
expiry_time = session['expiry_time'] || Time.now + 10 expiry_time = session['expiry_time'] || Time.now + 10
if expiry_time < Time.now if expiry_time < Time.now
# Too late, matey... bang goes your session! # Too late, matey... bang goes your session!
reset_session reset_session
else else
# Okay, you get another hour # Okay, you get another hour
session['expiry_time'] = Time.now + (60*60) session['expiry_time'] = Time.now + (60*60)
end end
end end
end end
end end
def render_failure message, status = 404 def render_failure message, status = 404
render :text => message, :status => status render :text => message, :status => status
end end
# def rescue_action(exception) # def rescue_action(exception)
# log_error(exception) if logger # log_error(exception) if logger
# respond_to do |format| # respond_to do |format|
# format.html do # format.html do
# notify :warning, "An error occurred on the server." # notify :warning, "An error occurred on the server."
# render :action => "index" # render :action => "index"
# end # end
# format.js { render :action => 'error' } # format.js { render :action => 'error' }
# format.xml { render :text => 'An error occurred on the server.' + $! } # format.xml { render :text => 'An error occurred on the server.' + $! }
# end # end
# end # end
# Returns a count of next actions in the given context or project The result # Returns a count of next actions in the given context or project The result
# is count and a string descriptor, correctly pluralised if there are no # is count and a string descriptor, correctly pluralised if there are no
# actions or multiple actions # actions or multiple actions
# #
def count_undone_todos_phrase(todos_parent, string="actions") def count_undone_todos_phrase(todos_parent, string="actions")
count = count_undone_todos(todos_parent) count = count_undone_todos(todos_parent)
if count == 1 if count == 1
word = string.singularize word = string.singularize
else else
word = string.pluralize word = string.pluralize
end end
return count.to_s + "&nbsp;" + word return count.to_s + "&nbsp;" + word
end end
def count_undone_todos(todos_parent) def count_undone_todos(todos_parent)
if todos_parent.nil? if todos_parent.nil?
count = 0 count = 0
elsif (todos_parent.is_a?(Project) && todos_parent.hidden?) elsif (todos_parent.is_a?(Project) && todos_parent.hidden?)
count = eval "@project_project_hidden_todo_counts[#{todos_parent.id}]" count = eval "@project_project_hidden_todo_counts[#{todos_parent.id}]"
else else
count = eval "@#{todos_parent.class.to_s.downcase}_not_done_counts[#{todos_parent.id}]" count = eval "@#{todos_parent.class.to_s.downcase}_not_done_counts[#{todos_parent.id}]"
end end
count || 0 count || 0
end end
# Convert a date object to the format specified in the user's preferences in # Convert a date object to the format specified in the user's preferences in
# config/settings.yml # config/settings.yml
# #
def format_date(date) def format_date(date)
if date if date
date_format = prefs.date_format date_format = prefs.date_format
formatted_date = date.in_time_zone(prefs.time_zone).strftime("#{date_format}") formatted_date = date.in_time_zone(prefs.time_zone).strftime("#{date_format}")
else else
formatted_date = '' formatted_date = ''
end end
formatted_date formatted_date
end end
# Uses RedCloth to transform text using either Textile or Markdown Need to # Uses RedCloth to transform text using either Textile or Markdown Need to
# require redcloth above RedCloth 3.0 or greater is needed to use Markdown, # require redcloth above RedCloth 3.0 or greater is needed to use Markdown,
# otherwise it only handles Textile # otherwise it only handles Textile
# #
def markdown(text) def markdown(text)
RedCloth.new(text).to_html RedCloth.new(text).to_html
end end
def build_default_project_context_name_map(projects) def build_default_project_context_name_map(projects)
Hash[*projects.reject{ |p| p.default_context.nil? }.map{ |p| [p.name, p.default_context.name] }.flatten].to_json Hash[*projects.reject{ |p| p.default_context.nil? }.map{ |p| [p.name, p.default_context.name] }.flatten].to_json
end end
# Here's the concept behind this "mobile content negotiation" hack: In # Here's the concept behind this "mobile content negotiation" hack: In
# addition to the main, AJAXy Web UI, Tracks has a lightweight low-feature # addition to the main, AJAXy Web UI, Tracks has a lightweight low-feature
# 'mobile' version designed to be suitablef or use from a phone or PDA. It # 'mobile' version designed to be suitablef or use from a phone or PDA. It
# makes some sense that tne pages of that mobile version are simply alternate # makes some sense that tne pages of that mobile version are simply alternate
# representations of the same Todo resources. The implementation goal was to # representations of the same Todo resources. The implementation goal was to
# treat mobile as another format and be able to use respond_to to render both # treat mobile as another format and be able to use respond_to to render both
# versions. Unfortunately, I ran into a lot of trouble simply registering a # versions. Unfortunately, I ran into a lot of trouble simply registering a
# new mime type 'text/html' with format :m because :html already is linked to # new mime type 'text/html' with format :m because :html already is linked to
# that mime type and the new registration was forcing all html requests to be # that mime type and the new registration was forcing all html requests to be
# rendered in the mobile view. The before_filter and after_filter hackery # rendered in the mobile view. The before_filter and after_filter hackery
# below accomplishs that implementation goal by using a 'fake' mime type # below accomplishs that implementation goal by using a 'fake' mime type
# during the processing and then setting it to 'text/html' in an # during the processing and then setting it to 'text/html' in an
# 'after_filter' -LKM 2007-04-01 # 'after_filter' -LKM 2007-04-01
def mobile? def mobile?
return params[:format] == 'm' return params[:format] == 'm'
end end
def enable_mobile_content_negotiation def enable_mobile_content_negotiation
if mobile? if mobile?
request.format = :m request.format = :m
end end
end end
def create_todo_from_recurring_todo(rt, date=nil) def create_todo_from_recurring_todo(rt, date=nil)
# create todo and initialize with data from recurring_todo rt # create todo and initialize with data from recurring_todo rt
todo = current_user.todos.build( { :description => rt.description, :notes => rt.notes, :project_id => rt.project_id, :context_id => rt.context_id}) todo = current_user.todos.build( { :description => rt.description, :notes => rt.notes, :project_id => rt.project_id, :context_id => rt.context_id})
# set dates # set dates
todo.recurring_todo_id = rt.id todo.recurring_todo_id = rt.id
todo.due = rt.get_due_date(date) todo.due = rt.get_due_date(date)
show_from_date = rt.get_show_from_date(date) show_from_date = rt.get_show_from_date(date)
if show_from_date.nil? if show_from_date.nil?
todo.show_from=nil todo.show_from=nil
else else
# make sure that show_from is not in the past # make sure that show_from is not in the past
todo.show_from = show_from_date < Time.zone.now ? nil : show_from_date todo.show_from = show_from_date < Time.zone.now ? nil : show_from_date
end end
saved = todo.save saved = todo.save
if saved if saved
todo.tag_with(rt.tag_list, current_user) todo.tag_with(rt.tag_list)
todo.tags.reload todo.tags.reload
end end
# increate number of occurences created from recurring todo # increate number of occurences created from recurring todo
rt.inc_occurences rt.inc_occurences
# mark recurring todo complete if there are no next actions left # mark recurring todo complete if there are no next actions left
checkdate = todo.due.nil? ? todo.show_from : todo.due checkdate = todo.due.nil? ? todo.show_from : todo.due
rt.toggle_completion! unless rt.has_next_todo(checkdate) rt.toggle_completion! unless rt.has_next_todo(checkdate)
return saved ? todo : nil return saved ? todo : nil
end end
protected protected
def admin_login_required def admin_login_required
unless User.find_by_id_and_is_admin(session['user_id'], true) unless User.find_by_id_and_is_admin(session['user_id'], true)
render :text => "401 Unauthorized: Only admin users are allowed access to this function.", :status => 401 render :text => "401 Unauthorized: Only admin users are allowed access to this function.", :status => 401
return false return false
end end
end end
def redirect_back_or_home def redirect_back_or_home
respond_to do |format| respond_to do |format|
format.html { redirect_back_or_default home_url } format.html { redirect_back_or_default home_url }
format.m { redirect_back_or_default mobile_url } format.m { redirect_back_or_default mobile_url }
end end
end end
def boolean_param(param_name) def boolean_param(param_name)
return false if param_name.blank? return false if param_name.blank?
s = params[param_name] s = params[param_name]
return false if s.blank? || s == false || s =~ /^false$/i return false if s.blank? || s == false || s =~ /^false$/i
return true if s == true || s =~ /^true$/i return true if s == true || s =~ /^true$/i
raise ArgumentError.new("invalid value for Boolean: \"#{s}\"") raise ArgumentError.new("invalid value for Boolean: \"#{s}\"")
end end
def self.openid_enabled? def self.openid_enabled?
Tracks::Config.openid_enabled? Tracks::Config.openid_enabled?
end end
def openid_enabled? def openid_enabled?
self.class.openid_enabled? self.class.openid_enabled?
end end
private private
def parse_date_per_user_prefs( s ) def parse_date_per_user_prefs( s )
prefs.parse_date(s) prefs.parse_date(s)
end end
def init_data_for_sidebar def init_data_for_sidebar
@completed_projects = current_user.projects.completed @completed_projects = current_user.projects.completed
@hidden_projects = current_user.projects.hidden @hidden_projects = current_user.projects.hidden
@active_projects = current_user.projects.active @active_projects = current_user.projects.active
@active_contexts = current_user.contexts.active @active_contexts = current_user.contexts.active
@hidden_contexts = current_user.contexts.hidden @hidden_contexts = current_user.contexts.hidden
init_not_done_counts init_not_done_counts
if prefs.show_hidden_projects_in_sidebar if prefs.show_hidden_projects_in_sidebar
init_project_hidden_todo_counts(['project']) init_project_hidden_todo_counts(['project'])
end end
end end
def init_not_done_counts(parents = ['project','context']) def init_not_done_counts(parents = ['project','context'])
parents.each do |parent| parents.each do |parent|
eval("@#{parent}_not_done_counts = @#{parent}_not_done_counts || current_user.todos.active.count(:group => :#{parent}_id)") eval("@#{parent}_not_done_counts = @#{parent}_not_done_counts || current_user.todos.active.count(:group => :#{parent}_id)")
end end
end end
def init_project_hidden_todo_counts(parents = ['project','context']) def init_project_hidden_todo_counts(parents = ['project','context'])
parents.each do |parent| parents.each do |parent|
eval("@#{parent}_project_hidden_todo_counts = @#{parent}_project_hidden_todo_counts || current_user.todos.count(:conditions => ['state = ? or state = ?', 'project_hidden', 'active'], :group => :#{parent}_id)") eval("@#{parent}_project_hidden_todo_counts = @#{parent}_project_hidden_todo_counts || current_user.todos.count(:conditions => ['state = ? or state = ?', 'project_hidden', 'active'], :group => :#{parent}_id)")
end end
end end
# Set the contents of the flash message from a controller Usage: notify # Set the contents of the flash message from a controller Usage: notify
# :warning, "This is the message" Sets the flash of type 'warning' to "This is # :warning, "This is the message" Sets the flash of type 'warning' to "This is
# the message" # the message"
def notify(type, message) def notify(type, message)
flash[type] = message flash[type] = message
logger.error("ERROR: #{message}") if type == :error logger.error("ERROR: #{message}") if type == :error
end end
def set_time_zone def set_time_zone
Time.zone = current_user.prefs.time_zone if logged_in? Time.zone = current_user.prefs.time_zone if logged_in?
end end
end end

View file

@ -23,6 +23,7 @@ class ContextsController < ApplicationController
end end
def show def show
@contexts = current_user.contexts(true)
if (@context.nil?) if (@context.nil?)
respond_to do |format| respond_to do |format|
format.html { render :text => 'Context not found', :status => 404 } format.html { render :text => 'Context not found', :status => 404 }

View file

@ -1,268 +1,268 @@
class RecurringTodosController < ApplicationController class RecurringTodosController < ApplicationController
helper :todos, :recurring_todos helper :todos, :recurring_todos
append_before_filter :init, :only => [:index, :new, :edit] append_before_filter :init, :only => [:index, :new, :edit]
append_before_filter :get_recurring_todo_from_param, :only => [:destroy, :toggle_check, :toggle_star, :edit, :update] append_before_filter :get_recurring_todo_from_param, :only => [:destroy, :toggle_check, :toggle_star, :edit, :update]
def index def index
find_and_inactivate find_and_inactivate
@recurring_todos = current_user.recurring_todos.active @recurring_todos = current_user.recurring_todos.active
@completed_recurring_todos = current_user.recurring_todos.completed @completed_recurring_todos = current_user.recurring_todos.completed
@no_recurring_todos = @recurring_todos.size == 0 @no_recurring_todos = @recurring_todos.size == 0
@no_completed_recurring_todos = @completed_recurring_todos.size == 0 @no_completed_recurring_todos = @completed_recurring_todos.size == 0
@count = @recurring_todos.size @count = @recurring_todos.size
@page_title = "TRACKS::Recurring Actions" @page_title = "TRACKS::Recurring Actions"
end end
def new def new
end end
def show def show
end end
def edit def edit
respond_to do |format| respond_to do |format|
format.js format.js
end end
end end
def update def update
@recurring_todo.tag_with(params[:tag_list], current_user) if params[:tag_list] @recurring_todo.tag_with(params[:tag_list]) if params[:tag_list]
@original_item_context_id = @recurring_todo.context_id @original_item_context_id = @recurring_todo.context_id
@original_item_project_id = @recurring_todo.project_id @original_item_project_id = @recurring_todo.project_id
# we needed to rename the recurring_period selector in the edit form because # we needed to rename the recurring_period selector in the edit form because
# the form for a new recurring todo and the edit form are on the same page. # the form for a new recurring todo and the edit form are on the same page.
# Same goes for start_from and end_date # Same goes for start_from and end_date
params['recurring_todo']['recurring_period']=params['recurring_edit_todo']['recurring_period'] params['recurring_todo']['recurring_period']=params['recurring_edit_todo']['recurring_period']
params['recurring_todo']['end_date']=parse_date_per_user_prefs(params['recurring_todo_edit_end_date']) params['recurring_todo']['end_date']=parse_date_per_user_prefs(params['recurring_todo_edit_end_date'])
params['recurring_todo']['start_from']=parse_date_per_user_prefs(params['recurring_todo_edit_start_from']) params['recurring_todo']['start_from']=parse_date_per_user_prefs(params['recurring_todo_edit_start_from'])
# update project # update project
if params['recurring_todo']['project_id'].blank? && !params['project_name'].nil? if params['recurring_todo']['project_id'].blank? && !params['project_name'].nil?
if params['project_name'] == 'None' if params['project_name'] == 'None'
project = Project.null_object project = Project.null_object
else else
project = current_user.projects.find_by_name(params['project_name'].strip) project = current_user.projects.find_by_name(params['project_name'].strip)
unless project unless project
project = current_user.projects.build project = current_user.projects.build
project.name = params['project_name'].strip project.name = params['project_name'].strip
project.save project.save
@new_project_created = true @new_project_created = true
end end
end end
params["recurring_todo"]["project_id"] = project.id params["recurring_todo"]["project_id"] = project.id
end end
# update context # update context
if params['recurring_todo']['context_id'].blank? && !params['context_name'].blank? if params['recurring_todo']['context_id'].blank? && !params['context_name'].blank?
context = current_user.contexts.find_by_name(params['context_name'].strip) context = current_user.contexts.find_by_name(params['context_name'].strip)
unless context unless context
context = current_user.contexts.build context = current_user.contexts.build
context.name = params['context_name'].strip context.name = params['context_name'].strip
context.save context.save
@new_context_created = true @new_context_created = true
end end
params["recurring_todo"]["context_id"] = context.id params["recurring_todo"]["context_id"] = context.id
end end
params["recurring_todo"]["weekly_return_monday"]=' ' if params["recurring_todo"]["weekly_return_monday"].nil? params["recurring_todo"]["weekly_return_monday"]=' ' if params["recurring_todo"]["weekly_return_monday"].nil?
params["recurring_todo"]["weekly_return_tuesday"]=' ' if params["recurring_todo"]["weekly_return_tuesday"].nil? params["recurring_todo"]["weekly_return_tuesday"]=' ' if params["recurring_todo"]["weekly_return_tuesday"].nil?
params["recurring_todo"]["weekly_return_wednesday"]=' ' if params["recurring_todo"]["weekly_return_wednesday"].nil? params["recurring_todo"]["weekly_return_wednesday"]=' ' if params["recurring_todo"]["weekly_return_wednesday"].nil?
params["recurring_todo"]["weekly_return_thursday"]=' ' if params["recurring_todo"]["weekly_return_thursday"].nil? params["recurring_todo"]["weekly_return_thursday"]=' ' if params["recurring_todo"]["weekly_return_thursday"].nil?
params["recurring_todo"]["weekly_return_friday"]=' ' if params["recurring_todo"]["weekly_return_friday"].nil? params["recurring_todo"]["weekly_return_friday"]=' ' if params["recurring_todo"]["weekly_return_friday"].nil?
params["recurring_todo"]["weekly_return_saturday"]=' ' if params["recurring_todo"]["weekly_return_saturday"].nil? params["recurring_todo"]["weekly_return_saturday"]=' ' if params["recurring_todo"]["weekly_return_saturday"].nil?
params["recurring_todo"]["weekly_return_sunday"]=' ' if params["recurring_todo"]["weekly_return_sunday"].nil? params["recurring_todo"]["weekly_return_sunday"]=' ' if params["recurring_todo"]["weekly_return_sunday"].nil?
@saved = @recurring_todo.update_attributes params["recurring_todo"] @saved = @recurring_todo.update_attributes params["recurring_todo"]
respond_to do |format| respond_to do |format|
format.js format.js
end end
end end
def create def create
p = RecurringTodoCreateParamsHelper.new(params) p = RecurringTodoCreateParamsHelper.new(params)
p.attributes['end_date']=parse_date_per_user_prefs(p.attributes['end_date']) p.attributes['end_date']=parse_date_per_user_prefs(p.attributes['end_date'])
p.attributes['start_from']=parse_date_per_user_prefs(p.attributes['start_from']) p.attributes['start_from']=parse_date_per_user_prefs(p.attributes['start_from'])
@recurring_todo = current_user.recurring_todos.build(p.selector_attributes) @recurring_todo = current_user.recurring_todos.build(p.selector_attributes)
@recurring_todo.update_attributes(p.attributes) @recurring_todo.update_attributes(p.attributes)
if p.project_specified_by_name? if p.project_specified_by_name?
project = current_user.projects.find_or_create_by_name(p.project_name) project = current_user.projects.find_or_create_by_name(p.project_name)
@new_project_created = project.new_record_before_save? @new_project_created = project.new_record_before_save?
@recurring_todo.project_id = project.id @recurring_todo.project_id = project.id
end end
if p.context_specified_by_name? if p.context_specified_by_name?
context = current_user.contexts.find_or_create_by_name(p.context_name) context = current_user.contexts.find_or_create_by_name(p.context_name)
@new_context_created = context.new_record_before_save? @new_context_created = context.new_record_before_save?
@recurring_todo.context_id = context.id @recurring_todo.context_id = context.id
end end
@recurring_saved = @recurring_todo.save @recurring_saved = @recurring_todo.save
unless (@recurring_saved == false) || p.tag_list.blank? unless (@recurring_saved == false) || p.tag_list.blank?
@recurring_todo.tag_with(p.tag_list, current_user) @recurring_todo.tag_with(p.tag_list)
@recurring_todo.tags.reload @recurring_todo.tags.reload
end end
if @recurring_saved if @recurring_saved
@message = "The recurring todo was saved" @message = "The recurring todo was saved"
@todo_saved = create_todo_from_recurring_todo(@recurring_todo).nil? == false @todo_saved = create_todo_from_recurring_todo(@recurring_todo).nil? == false
if @todo_saved if @todo_saved
@message += " / created a new todo" @message += " / created a new todo"
else else
@message += " / did not create todo" @message += " / did not create todo"
end end
@count = current_user.recurring_todos.active.count @count = current_user.recurring_todos.active.count
else else
@message = "Error saving recurring todo" @message = "Error saving recurring todo"
end end
respond_to do |format| respond_to do |format|
format.js format.js
end end
end end
def destroy def destroy
# remove all references to this recurring todo # remove all references to this recurring todo
@todos = @recurring_todo.todos @todos = @recurring_todo.todos
@number_of_todos = @todos.size @number_of_todos = @todos.size
@todos.each do |t| @todos.each do |t|
t.recurring_todo_id = nil t.recurring_todo_id = nil
t.save t.save
end end
# delete the recurring todo # delete the recurring todo
@saved = @recurring_todo.destroy @saved = @recurring_todo.destroy
@remaining = current_user.recurring_todos.count @remaining = current_user.recurring_todos.count
respond_to do |format| respond_to do |format|
format.html do format.html do
if @saved if @saved
notify :notice, "Successfully deleted recurring action", 2.0 notify :notice, "Successfully deleted recurring action", 2.0
redirect_to :action => 'index' redirect_to :action => 'index'
else else
notify :error, "Failed to delete the recurring action", 2.0 notify :error, "Failed to delete the recurring action", 2.0
redirect_to :action => 'index' redirect_to :action => 'index'
end end
end end
format.js do format.js do
render render
end end
end end
end end
def toggle_check def toggle_check
@saved = @recurring_todo.toggle_completion! @saved = @recurring_todo.toggle_completion!
@count = current_user.recurring_todos.active.count @count = current_user.recurring_todos.active.count
@remaining = @count @remaining = @count
if @recurring_todo.active? if @recurring_todo.active?
@remaining = current_user.recurring_todos.completed.count @remaining = current_user.recurring_todos.completed.count
# from completed back to active -> check if there is an active todo # from completed back to active -> check if there is an active todo
# current_user.todos.count(:all, {:conditions => ["state = ? AND recurring_todo_id = ?", 'active',params[:id]]}) # current_user.todos.count(:all, {:conditions => ["state = ? AND recurring_todo_id = ?", 'active',params[:id]]})
@active_todos = @recurring_todo.todos.active.count @active_todos = @recurring_todo.todos.active.count
# create todo if there is no active todo belonging to the activated # create todo if there is no active todo belonging to the activated
# recurring_todo # recurring_todo
@new_recurring_todo = create_todo_from_recurring_todo(@recurring_todo) if @active_todos == 0 @new_recurring_todo = create_todo_from_recurring_todo(@recurring_todo) if @active_todos == 0
end end
respond_to do |format| respond_to do |format|
format.js format.js
end end
end end
def toggle_star def toggle_star
@recurring_todo.toggle_star! @recurring_todo.toggle_star!
@saved = @recurring_todo.save! @saved = @recurring_todo.save!
respond_to do |format| respond_to do |format|
format.js format.js
end end
end end
class RecurringTodoCreateParamsHelper class RecurringTodoCreateParamsHelper
def initialize(params) def initialize(params)
@params = params['request'] || params @params = params['request'] || params
@attributes = params['request'] && params['request']['recurring_todo'] || params['recurring_todo'] @attributes = params['request'] && params['request']['recurring_todo'] || params['recurring_todo']
# make sure all selectors (recurring_period, recurrence_selector, # make sure all selectors (recurring_period, recurrence_selector,
# daily_selector, monthly_selector and yearly_selector) are first in hash # daily_selector, monthly_selector and yearly_selector) are first in hash
# so that they are processed first by the model # so that they are processed first by the model
@selector_attributes = { @selector_attributes = {
'recurring_period' => @attributes['recurring_period'], 'recurring_period' => @attributes['recurring_period'],
'daily_selector' => @attributes['daily_selector'], 'daily_selector' => @attributes['daily_selector'],
'monthly_selector' => @attributes['monthly_selector'], 'monthly_selector' => @attributes['monthly_selector'],
'yearly_selector' => @attributes['yearly_selector'] 'yearly_selector' => @attributes['yearly_selector']
} }
end end
def attributes def attributes
@attributes @attributes
end end
def selector_attributes def selector_attributes
return @selector_attributes return @selector_attributes
end end
def project_name def project_name
@params['project_name'].strip unless @params['project_name'].nil? @params['project_name'].strip unless @params['project_name'].nil?
end end
def context_name def context_name
@params['context_name'].strip unless @params['context_name'].nil? @params['context_name'].strip unless @params['context_name'].nil?
end end
def tag_list def tag_list
@params['tag_list'] @params['tag_list']
end end
def project_specified_by_name? def project_specified_by_name?
return false unless @attributes['project_id'].blank? return false unless @attributes['project_id'].blank?
return false if project_name.blank? return false if project_name.blank?
return false if project_name == 'None' return false if project_name == 'None'
true true
end end
def context_specified_by_name? def context_specified_by_name?
return false unless @attributes['context_id'].blank? return false unless @attributes['context_id'].blank?
return false if context_name.blank? return false if context_name.blank?
true true
end end
end end
private private
def init def init
@days_of_week = [ ['Sunday',0], ['Monday',1], ['Tuesday', 2], ['Wednesday',3], ['Thursday',4], ['Friday',5], ['Saturday',6]] @days_of_week = [ ['Sunday',0], ['Monday',1], ['Tuesday', 2], ['Wednesday',3], ['Thursday',4], ['Friday',5], ['Saturday',6]]
@months_of_year = [ @months_of_year = [
['January',1], ['Februari',2], ['March', 3], ['April',4], ['May',5], ['June',6], ['January',1], ['Februari',2], ['March', 3], ['April',4], ['May',5], ['June',6],
['July',7], ['August',8], ['September',9], ['October', 10], ['November', 11], ['December',12]] ['July',7], ['August',8], ['September',9], ['October', 10], ['November', 11], ['December',12]]
@xth_day = [['first',1],['second',2],['third',3],['fourth',4],['last',5]] @xth_day = [['first',1],['second',2],['third',3],['fourth',4],['last',5]]
@projects = current_user.projects.find(:all, :include => [:default_context]) @projects = current_user.projects.find(:all, :include => [:default_context])
@contexts = current_user.contexts.find(:all) @contexts = current_user.contexts.find(:all)
@default_project_context_name_map = build_default_project_context_name_map(@projects).to_json @default_project_context_name_map = build_default_project_context_name_map(@projects).to_json
end end
def get_recurring_todo_from_param def get_recurring_todo_from_param
@recurring_todo = current_user.recurring_todos.find(params[:id]) @recurring_todo = current_user.recurring_todos.find(params[:id])
end end
def find_and_inactivate def find_and_inactivate
# find active recurring todos without active todos and inactivate them # find active recurring todos without active todos and inactivate them
recurring_todos = current_user.recurring_todos.active recurring_todos = current_user.recurring_todos.active
recurring_todos.each { |rt| rt.toggle_completion! if rt.todos.not_completed.count == 0} recurring_todos.each { |rt| rt.toggle_completion! if rt.todos.not_completed.count == 0}
end end
end end

File diff suppressed because it is too large Load diff

View file

@ -1,287 +1,287 @@
module TodosHelper module TodosHelper
# #require 'users_controller' Counts the number of incomplete items in the # #require 'users_controller' Counts the number of incomplete items in the
# specified context # specified context
# #
def count_items(context) def count_items(context)
count = Todo.find_all("done=0 AND context_id=#{context.id}").length count = Todo.find_all("done=0 AND context_id=#{context.id}").length
end end
def form_remote_tag_edit_todo( &block ) def form_remote_tag_edit_todo( &block )
form_tag( form_tag(
todo_path(@todo), { todo_path(@todo), {
:method => :put, :method => :put,
:id => dom_id(@todo, 'form'), :id => dom_id(@todo, 'form'),
:class => dom_id(@todo, 'form') + " inline-form edit_todo_form" }, :class => dom_id(@todo, 'form') + " inline-form edit_todo_form" },
&block ) &block )
apply_behavior 'form.edit_todo_form', make_remote_form( apply_behavior 'form.edit_todo_form', make_remote_form(
:method => :put, :method => :put,
:before => "this.down('button.positive').startWaiting()", :before => "this.down('button.positive').startWaiting()",
:loaded => "this.down('button.positive').stopWaiting()", :loaded => "this.down('button.positive').stopWaiting()",
:condition => "!(this.down('button.positive').isWaiting())"), :condition => "!(this.down('button.positive').isWaiting())"),
:prevent_default => true :prevent_default => true
end end
def set_behavior_for_delete_icon def set_behavior_for_delete_icon
parameters = "_source_view=#{@source_view}" parameters = "_source_view=#{@source_view}"
parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag' parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
apply_behavior '.item-container a.delete_icon:click', :prevent_default => true do |page| apply_behavior '.item-container a.delete_icon:click', :prevent_default => true do |page|
page.confirming "'Are you sure that you want to ' + this.title + '?'" do page.confirming "'Are you sure that you want to ' + this.title + '?'" do
page << "itemContainer = this.up('.item-container'); itemContainer.startWaiting();" page << "itemContainer = this.up('.item-container'); itemContainer.startWaiting();"
page << remote_to_href(:method => 'delete', :with => "'#{parameters}'", :complete => "itemContainer.stopWaiting();") page << remote_to_href(:method => 'delete', :with => "'#{parameters}'", :complete => "itemContainer.stopWaiting();")
end end
end end
end end
def remote_delete_icon def remote_delete_icon
str = link_to( image_tag_for_delete, str = link_to( image_tag_for_delete,
todo_path(@todo), :id => "delete_icon_"+@todo.id.to_s, todo_path(@todo), :id => "delete_icon_"+@todo.id.to_s,
:class => "icon delete_icon", :title => "delete the action '#{@todo.description}'") :class => "icon delete_icon", :title => "delete the action '#{@todo.description}'")
set_behavior_for_delete_icon set_behavior_for_delete_icon
str str
end end
def set_behavior_for_star_icon def set_behavior_for_star_icon
apply_behavior '.item-container a.star_item:click', apply_behavior '.item-container a.star_item:click',
remote_to_href(:method => 'put', :with => "{ _source_view : '#{@source_view}' }"), remote_to_href(:method => 'put', :with => "{ _source_view : '#{@source_view}' }"),
:prevent_default => true :prevent_default => true
end end
def remote_star_icon def remote_star_icon
str = link_to( image_tag_for_star(@todo), str = link_to( image_tag_for_star(@todo),
toggle_star_todo_path(@todo), toggle_star_todo_path(@todo),
:class => "icon star_item", :title => "star the action '#{@todo.description}'") :class => "icon star_item", :title => "star the action '#{@todo.description}'")
set_behavior_for_star_icon set_behavior_for_star_icon
str str
end end
def set_behavior_for_edit_icon def set_behavior_for_edit_icon
parameters = "_source_view=#{@source_view}" parameters = "_source_view=#{@source_view}"
parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag' parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
apply_behavior '.item-container a.edit_icon:click', :prevent_default => true do |page| apply_behavior '.item-container a.edit_icon:click', :prevent_default => true do |page|
page << "Effect.Pulsate(this);" page << "Effect.Pulsate(this);"
page << remote_to_href(:method => 'get', :with => "'#{parameters}'") page << remote_to_href(:method => 'get', :with => "'#{parameters}'")
end end
end end
def remote_edit_icon def remote_edit_icon
if !@todo.completed? if !@todo.completed?
str = link_to( image_tag_for_edit(@todo), str = link_to( image_tag_for_edit(@todo),
edit_todo_path(@todo), edit_todo_path(@todo),
:class => "icon edit_icon") :class => "icon edit_icon")
set_behavior_for_edit_icon set_behavior_for_edit_icon
else else
str = '<a class="icon">' + image_tag("blank.png") + "</a> " str = '<a class="icon">' + image_tag("blank.png") + "</a> "
end end
str str
end end
def set_behavior_for_toggle_checkbox def set_behavior_for_toggle_checkbox
parameters = "_source_view=#{@source_view}" parameters = "_source_view=#{@source_view}"
parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag' parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
apply_behavior '.item-container input.item-checkbox:click', apply_behavior '.item-container input.item-checkbox:click',
remote_function(:url => javascript_variable('this.value'), :method => 'put', remote_function(:url => javascript_variable('this.value'), :method => 'put',
:with => "'#{parameters}'") :with => "'#{parameters}'")
end end
def remote_toggle_checkbox def remote_toggle_checkbox
str = check_box_tag('item_id', toggle_check_todo_path(@todo), @todo.completed?, :class => 'item-checkbox') str = check_box_tag('item_id', toggle_check_todo_path(@todo), @todo.completed?, :class => 'item-checkbox')
set_behavior_for_toggle_checkbox set_behavior_for_toggle_checkbox
str str
end end
def date_span def date_span
if @todo.completed? if @todo.completed?
"<span class=\"grey\">#{format_date( @todo.completed_at )}</span>" "<span class=\"grey\">#{format_date( @todo.completed_at )}</span>"
elsif @todo.deferred? elsif @todo.deferred?
show_date( @todo.show_from ) show_date( @todo.show_from )
else else
due_date( @todo.due ) due_date( @todo.due )
end end
end end
def tag_list_text def tag_list_text
@todo.tags.collect{|t| t.name}.join(', ') @todo.tags.collect{|t| t.name}.join(', ')
end end
def tag_list def tag_list
tags_except_starred = @todo.tags.reject{|t| t.name == Todo::STARRED_TAG_NAME} tags_except_starred = @todo.tags.reject{|t| t.name == Todo::STARRED_TAG_NAME}
tag_list = tags_except_starred.collect{|t| "<span class=\"tag #{t.name.gsub(' ','-')}\">" + link_to(t.name, :controller => "todos", :action => "tag", :id => t.name) + "</span>"}.join('') tag_list = tags_except_starred.collect{|t| "<span class=\"tag #{t.name.gsub(' ','-')}\">" + link_to(t.name, :controller => "todos", :action => "tag", :id => t.name) + "</span>"}.join('')
"<span class='tags'>#{tag_list}</span>" "<span class='tags'>#{tag_list}</span>"
end end
def tag_list_mobile def tag_list_mobile
tags_except_starred = @todo.tags.reject{|t| t.name == Todo::STARRED_TAG_NAME} tags_except_starred = @todo.tags.reject{|t| t.name == Todo::STARRED_TAG_NAME}
# removed the link. TODO: add link to mobile view of tagged actions # removed the link. TODO: add link to mobile view of tagged actions
tag_list = tags_except_starred.collect{|t| tag_list = tags_except_starred.collect{|t|
"<span class=\"tag\">" + "<span class=\"tag\">" +
link_to(t.name, {:action => "tag", :controller => "todos", :id => t.name+".m"}) + link_to(t.name, {:action => "tag", :controller => "todos", :id => t.name+".m"}) +
"</span>"}.join('') "</span>"}.join('')
if tag_list.empty? then "" else "<span class=\"tags\">#{tag_list}</span>" end if tag_list.empty? then "" else "<span class=\"tags\">#{tag_list}</span>" end
end end
def deferred_due_date def deferred_due_date
if @todo.deferred? && @todo.due if @todo.deferred? && @todo.due
"(action due on #{format_date(@todo.due)})" "(action due on #{format_date(@todo.due)})"
end end
end end
def project_and_context_links(parent_container_type, opts = {}) def project_and_context_links(parent_container_type, opts = {})
str = '' str = ''
if @todo.completed? if @todo.completed?
str += @todo.context.name unless opts[:suppress_context] str += @todo.context.name unless opts[:suppress_context]
should_suppress_project = opts[:suppress_project] || @todo.project.nil? should_suppress_project = opts[:suppress_project] || @todo.project.nil?
str += ", " unless str.blank? || should_suppress_project str += ", " unless str.blank? || should_suppress_project
str += @todo.project.name unless should_suppress_project str += @todo.project.name unless should_suppress_project
str = "(#{str})" unless str.blank? str = "(#{str})" unless str.blank?
else else
if (['project', 'tag', 'stats', 'search'].include?(parent_container_type)) if (['project', 'tag', 'stats', 'search'].include?(parent_container_type))
str << item_link_to_context( @todo ) str << item_link_to_context( @todo )
end end
if (['context', 'tickler', 'tag', 'stats', 'search'].include?(parent_container_type)) && @todo.project_id if (['context', 'tickler', 'tag', 'stats', 'search'].include?(parent_container_type)) && @todo.project_id
str << item_link_to_project( @todo ) str << item_link_to_project( @todo )
end end
end end
return str return str
end end
# Uses the 'staleness_starts' value from settings.yml (in days) to colour the # Uses the 'staleness_starts' value from settings.yml (in days) to colour the
# background of the action appropriately according to the age of the creation # background of the action appropriately according to the age of the creation
# date: # date:
# * l1: created more than 1 x staleness_starts, but < 2 x staleness_starts # * l1: created more than 1 x staleness_starts, but < 2 x staleness_starts
# * l2: created more than 2 x staleness_starts, but < 3 x staleness_starts # * l2: created more than 2 x staleness_starts, but < 3 x staleness_starts
# * l3: created more than 3 x staleness_starts # * l3: created more than 3 x staleness_starts
# #
def staleness_class(item) def staleness_class(item)
if item.due || item.completed? if item.due || item.completed?
return "" return ""
elsif item.created_at < user_time - (prefs.staleness_starts * 3).days elsif item.created_at < user_time - (prefs.staleness_starts * 3).days
return " stale_l3" return " stale_l3"
elsif item.created_at < user_time - (prefs.staleness_starts * 2).days elsif item.created_at < user_time - (prefs.staleness_starts * 2).days
return " stale_l2" return " stale_l2"
elsif item.created_at < user_time - (prefs.staleness_starts).days elsif item.created_at < user_time - (prefs.staleness_starts).days
return " stale_l1" return " stale_l1"
else else
return "" return ""
end end
end end
# Check show_from date in comparison to today's date Flag up date # Check show_from date in comparison to today's date Flag up date
# appropriately with a 'traffic light' colour code # appropriately with a 'traffic light' colour code
# #
def show_date(d) def show_date(d)
if d == nil if d == nil
return "" return ""
end end
days = days_from_today(d) days = days_from_today(d)
case days case days
# overdue or due very soon! sound the alarm! # overdue or due very soon! sound the alarm!
when -1000..-1 when -1000..-1
"<a title=\"" + format_date(d) + "\"><span class=\"red\">Scheduled to show " + (days * -1).to_s + " days ago</span></a> " "<a title=\"" + format_date(d) + "\"><span class=\"red\">Scheduled to show " + (days * -1).to_s + " days ago</span></a> "
when 0 when 0
"<a title=\"" + format_date(d) + "\"><span class=\"amber\">Show Today</span></a> " "<a title=\"" + format_date(d) + "\"><span class=\"amber\">Show Today</span></a> "
when 1 when 1
"<a title=\"" + format_date(d) + "\"><span class=\"amber\">Show Tomorrow</span></a> " "<a title=\"" + format_date(d) + "\"><span class=\"amber\">Show Tomorrow</span></a> "
# due 2-7 days away # due 2-7 days away
when 2..7 when 2..7
if prefs.due_style == Preference.due_styles[:due_on] if prefs.due_style == Preference.due_styles[:due_on]
"<a title=\"" + format_date(d) + "\"><span class=\"orange\">Show on " + d.strftime("%A") + "</span></a> " "<a title=\"" + format_date(d) + "\"><span class=\"orange\">Show on " + d.strftime("%A") + "</span></a> "
else else
"<a title=\"" + format_date(d) + "\"><span class=\"orange\">Show in " + days.to_s + " days</span></a> " "<a title=\"" + format_date(d) + "\"><span class=\"orange\">Show in " + days.to_s + " days</span></a> "
end end
# more than a week away - relax # more than a week away - relax
else else
"<a title=\"" + format_date(d) + "\"><span class=\"green\">Show in " + days.to_s + " days</span></a> " "<a title=\"" + format_date(d) + "\"><span class=\"green\">Show in " + days.to_s + " days</span></a> "
end end
end end
def calendar_setup( input_field ) def calendar_setup( input_field )
str = "Calendar.setup({ ifFormat:\"#{prefs.date_format}\"" str = "Calendar.setup({ ifFormat:\"#{prefs.date_format}\""
str << ",firstDay:#{prefs.week_starts},showOthers:true,range:[2004, 2010]" str << ",firstDay:#{prefs.week_starts},showOthers:true,range:[2004, 2010]"
str << ",step:1,inputField:\"" + input_field + "\",cache:true,align:\"TR\" })\n" str << ",step:1,inputField:\"" + input_field + "\",cache:true,align:\"TR\" })\n"
javascript_tag str javascript_tag str
end end
def item_container_id (todo) def item_container_id (todo)
if source_view_is :project if source_view_is :project
return "p#{todo.project_id}" if todo.active? return "p#{todo.project_id}" if todo.active?
return "tickler" if todo.deferred? return "tickler" if todo.deferred?
end end
return "c#{todo.context_id}" return "c#{todo.context_id}"
end end
def should_show_new_item def should_show_new_item
if @todo.project.nil? == false if @todo.project.nil? == false
# do not show new actions that were added to hidden or completed projects # do not show new actions that were added to hidden or completed projects
# on home page and context page # on home page and context page
return false if source_view_is(:todo) && (@todo.project.hidden? || @todo.project.completed?) return false if source_view_is(:todo) && (@todo.project.hidden? || @todo.project.completed?)
return false if source_view_is(:context) && (@todo.project.hidden? || @todo.project.completed?) return false if source_view_is(:context) && (@todo.project.hidden? || @todo.project.completed?)
end end
return true if source_view_is(:deferred) && @todo.deferred? return true if source_view_is(:deferred) && @todo.deferred?
return true if source_view_is(:project) && @todo.project.hidden? && @todo.project_hidden? return true if source_view_is(:project) && @todo.project.hidden? && @todo.project_hidden?
return true if source_view_is(:project) && @todo.deferred? return true if source_view_is(:project) && @todo.deferred?
return true if !source_view_is(:deferred) && @todo.active? return true if !source_view_is(:deferred) && @todo.active?
return false return false
end end
def parent_container_type def parent_container_type
return 'tickler' if source_view_is :deferred return 'tickler' if source_view_is :deferred
return 'project' if source_view_is :project return 'project' if source_view_is :project
return 'stats' if source_view_is :stats return 'stats' if source_view_is :stats
return 'context' return 'context'
end end
def empty_container_msg_div_id def empty_container_msg_div_id
return "tickler-empty-nd" if source_view_is(:project) && @todo.deferred? return "tickler-empty-nd" if source_view_is(:project) && @todo.deferred?
return "p#{@todo.project_id}empty-nd" if source_view_is :project return "p#{@todo.project_id}empty-nd" if source_view_is :project
return "c#{@todo.context_id}empty-nd" return "c#{@todo.context_id}empty-nd"
end end
def project_names_for_autocomplete def project_names_for_autocomplete
array_or_string_for_javascript( ['None'] + current_user.projects.active.collect{|p| escape_javascript(p.name) } ) array_or_string_for_javascript( ['None'] + current_user.projects.active.collect{|p| escape_javascript(p.name) } )
end end
def context_names_for_autocomplete def context_names_for_autocomplete
# #return array_or_string_for_javascript(['Create a new context']) if # #return array_or_string_for_javascript(['Create a new context']) if
# @contexts.empty? # @contexts.empty?
array_or_string_for_javascript( current_user.contexts.collect{|c| escape_javascript(c.name) } ) array_or_string_for_javascript( current_user.contexts.collect{|c| escape_javascript(c.name) } )
end end
def format_ical_notes(notes) def format_ical_notes(notes)
split_notes = notes.split(/\n/) split_notes = notes.split(/\n/)
joined_notes = split_notes.join("\\n") joined_notes = split_notes.join("\\n")
end end
def formatted_pagination(total) def formatted_pagination(total)
s = will_paginate(@todos) s = will_paginate(@todos)
(s.gsub /(<\/[^<]+>)/, '\1 ').chomp(' ') (s.gsub(/(<\/[^<]+>)/, '\1 ')).chomp(' ')
end end
def date_field_tag(name, id, value = nil, options = {}) def date_field_tag(name, id, value = nil, options = {})
text_field_tag name, value, {"size" => 12, "id" => id, "class" => "Date", "onfocus" => "Calendar.setup", "autocomplete" => "off"}.update(options.stringify_keys) text_field_tag name, value, {"size" => 12, "id" => id, "class" => "Date", "onfocus" => "Calendar.setup", "autocomplete" => "off"}.update(options.stringify_keys)
end end
private private
def image_tag_for_delete def image_tag_for_delete
image_tag("blank.png", :title =>"Delete action", :class=>"delete_item") image_tag("blank.png", :title =>"Delete action", :class=>"delete_item")
end end
def image_tag_for_edit(todo) def image_tag_for_edit(todo)
image_tag("blank.png", :title =>"Edit action", :class=>"edit_item", :id=> dom_id(todo, 'edit_icon')) image_tag("blank.png", :title =>"Edit action", :class=>"edit_item", :id=> dom_id(todo, 'edit_icon'))
end end
def image_tag_for_star(todo) def image_tag_for_star(todo)
class_str = todo.starred? ? "starred_todo" : "unstarred_todo" class_str = todo.starred? ? "starred_todo" : "unstarred_todo"
image_tag("blank.png", :title =>"Star action", :class => class_str) image_tag("blank.png", :title =>"Star action", :class => class_str)
end end
def defer_link(days) def defer_link(days)
link_to_remote image_tag("defer_#{days}.png", :alt => "Defer #{pluralize(days, 'day')}"), :url => {:controller => 'todos', :action => 'defer', :id => @todo.id, :days => days, :_source_view => (@source_view.underscore.gsub(/\s+/,'_') rescue "")} link_to_remote image_tag("defer_#{days}.png", :alt => "Defer #{pluralize(days, 'day')}"), :url => {:controller => 'todos', :action => 'defer', :id => @todo.id, :days => days, :_source_view => (@source_view.underscore.gsub(/\s+/,'_') rescue "")}
end end
end end

View file

@ -362,14 +362,14 @@ class RecurringTodo < ActiveRecord::Base
end end
def starred? def starred?
tags.any? {|tag| tag.name == Todo::STARRED_TAG_NAME} tags.any? {|tag| tag.name == Todo::STARRED_TAG_NAME }
end end
def get_due_date(previous) def get_due_date(previous)
case self.target case self.target
when 'due_date' when 'due_date'
return get_next_date(previous) return get_next_date(previous)
when 'show_from' when 'show_from_date'
# so leave due date empty # so leave due date empty
return nil return nil
else else
@ -623,10 +623,10 @@ class RecurringTodo < ActiveRecord::Base
def toggle_star! def toggle_star!
if starred? if starred?
delete_tags Todo::STARRED_TAG_NAME _remove_tags Todo::STARRED_TAG_NAME
tags.reload tags.reload
else else
add_tag Todo::STARRED_TAG_NAME _add_tags(Todo::STARRED_TAG_NAME)
tags.reload tags.reload
end end
starred? starred?

View file

@ -1,11 +1,48 @@
class Tag < ActiveRecord::Base
has_many_polymorphs :taggables,
:from => [:todos, :recurring_todos],
:through => :taggings,
:dependent => :destroy
def on(taggable, user) # The Tag model. This model is automatically generated and added to your app if
tagging = taggings.create :taggable => taggable, :user => user # you run the tagging generator included with has_many_polymorphs.
class Tag < ActiveRecord::Base
DELIMITER = "," # Controls how to split and join tagnames from strings. You may need to change the <tt>validates_format_of parameters</tt> if you change this.
JOIN_DELIMITER = ", "
# If database speed becomes an issue, you could remove these validations and
# rescue the ActiveRecord database constraint errors instead.
validates_presence_of :name
validates_uniqueness_of :name, :case_sensitive => false
# Change this validation if you need more complex tag names.
# validates_format_of :name, :with => /^[a-zA-Z0-9\_\-]+$/, :message => "can not contain special characters"
# Set up the polymorphic relationship.
has_many_polymorphs :taggables,
:from => [:todos, :recurring_todos],
:through => :taggings,
:dependent => :destroy,
:skip_duplicates => false,
:parent_extend => proc {
# Defined on the taggable models, not on Tag itself. Return the tagnames
# associated with this record as a string.
def to_s
self.map(&:name).sort.join(Tag::JOIN_DELIMITER)
end
}
# Callback to strip extra spaces from the tagname before saving it. If you
# allow tags to be renamed later, you might want to use the
# <tt>before_save</tt> callback instead.
def before_create
self.name = name.downcase.strip.squeeze(" ")
end end
end def on(taggable, user)
taggings.create :taggable => taggable, :user => user
end
# Tag::Error class. Raised by ActiveRecord::Base::TaggingExtensions if
# something goes wrong.
class Error < StandardError
end
end

View file

@ -1,11 +1,17 @@
class Tagging < ActiveRecord::Base
# The Tagging join model. This model is automatically generated and added to your app if you run the tagging generator included with has_many_polymorphs.
class Tagging < ActiveRecord::Base
belongs_to :tag belongs_to :tag
belongs_to :taggable, :polymorphic => true belongs_to :taggable, :polymorphic => true
belongs_to :user # belongs_to :user
# def before_destroy # If you also need to use <tt>acts_as_list</tt>, you will have to manage the tagging positions manually by creating decorated join records when you associate Tags with taggables.
# # disallow orphaned tags # acts_as_list :scope => :taggable
# # TODO: this doesn't seem to be working
# tag.destroy if tag.taggings.count < 2 # This callback makes sure that an orphaned <tt>Tag</tt> is deleted if it no longer tags anything.
# end def after_destroy
tag.destroy_without_callbacks if tag and tag.taggings.count == 0
end
end end

View file

@ -1,176 +1,176 @@
class Todo < ActiveRecord::Base class Todo < ActiveRecord::Base
belongs_to :context belongs_to :context
belongs_to :project belongs_to :project
belongs_to :user belongs_to :user
belongs_to :recurring_todo belongs_to :recurring_todo
named_scope :active, :conditions => { :state => 'active' } named_scope :active, :conditions => { :state => 'active' }
named_scope :not_completed, :conditions => ['NOT state = ? ', 'completed'] named_scope :not_completed, :conditions => ['NOT state = ? ', 'completed']
named_scope :are_due, :conditions => ['NOT todos.due IS NULL'] named_scope :are_due, :conditions => ['NOT todos.due IS NULL']
STARRED_TAG_NAME = "starred" STARRED_TAG_NAME = "starred"
acts_as_state_machine :initial => :active, :column => 'state' acts_as_state_machine :initial => :active, :column => 'state'
# when entering active state, also remove completed_at date. Looks like :exit # when entering active state, also remove completed_at date. Looks like :exit
# of state completed is not run, see #679 # of state completed is not run, see #679
state :active, :enter => Proc.new { |t| t[:show_from], t.completed_at = nil, nil } state :active, :enter => Proc.new { |t| t[:show_from], t.completed_at = nil, nil }
state :project_hidden state :project_hidden
state :completed, :enter => Proc.new { |t| t.completed_at = Time.zone.now }, :exit => Proc.new { |t| t.completed_at = nil } state :completed, :enter => Proc.new { |t| t.completed_at = Time.zone.now }, :exit => Proc.new { |t| t.completed_at = nil }
state :deferred state :deferred
event :defer do event :defer do
transitions :to => :deferred, :from => [:active] transitions :to => :deferred, :from => [:active]
end end
event :complete do event :complete do
transitions :to => :completed, :from => [:active, :project_hidden, :deferred] transitions :to => :completed, :from => [:active, :project_hidden, :deferred]
end end
event :activate do event :activate do
transitions :to => :active, :from => [:project_hidden, :completed, :deferred] transitions :to => :active, :from => [:project_hidden, :completed, :deferred]
end end
event :hide do event :hide do
transitions :to => :project_hidden, :from => [:active, :deferred] transitions :to => :project_hidden, :from => [:active, :deferred]
end end
event :unhide do event :unhide do
transitions :to => :deferred, :from => [:project_hidden], :guard => Proc.new{|t| !t.show_from.blank? } transitions :to => :deferred, :from => [:project_hidden], :guard => Proc.new{|t| !t.show_from.blank? }
transitions :to => :active, :from => [:project_hidden] transitions :to => :active, :from => [:project_hidden]
end end
attr_protected :user attr_protected :user
# Description field can't be empty, and must be < 100 bytes Notes must be < # 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) # 60,000 bytes (65,000 actually, but I'm being cautious)
validates_presence_of :description validates_presence_of :description
validates_length_of :description, :maximum => 100 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 :show_from, :if => :deferred?
validates_presence_of :context validates_presence_of :context
def validate def validate
if !show_from.blank? && show_from < user.date if !show_from.blank? && show_from < user.date
errors.add("show_from", "must be a date in the future") errors.add("show_from", "must be a date in the future")
end end
end end
def toggle_completion! def toggle_completion!
saved = false saved = false
if completed? if completed?
saved = activate! saved = activate!
else else
saved = complete! saved = complete!
end end
return saved return saved
end end
def show_from def show_from
self[:show_from] self[:show_from]
end end
def show_from=(date) def show_from=(date)
# parse Date objects into the proper timezone # parse Date objects into the proper timezone
date = user.at_midnight(date) if (date.is_a? Date) date = user.at_midnight(date) if (date.is_a? Date)
activate! if deferred? && date.blank? activate! if deferred? && date.blank?
defer! if active? && !date.blank? && date > user.date defer! if active? && !date.blank? && date > user.date
self[:show_from] = date self[:show_from] = date
end end
alias_method :original_project, :project alias_method :original_project, :project
def project def project
original_project.nil? ? Project.null_object : original_project original_project.nil? ? Project.null_object : original_project
end end
alias_method :original_set_initial_state, :set_initial_state alias_method :original_set_initial_state, :set_initial_state
def set_initial_state def set_initial_state
if show_from && (show_from > user.date) if show_from && (show_from > user.date)
write_attribute self.class.state_column, 'deferred' write_attribute self.class.state_column, 'deferred'
else else
original_set_initial_state original_set_initial_state
end end
end end
alias_method :original_run_initial_state_actions, :run_initial_state_actions alias_method :original_run_initial_state_actions, :run_initial_state_actions
def run_initial_state_actions def run_initial_state_actions
# only run the initial state actions if the standard initial state hasn't # only run the initial state actions if the standard initial state hasn't
# been changed # been changed
if self.class.initial_state.to_sym == current_state if self.class.initial_state.to_sym == current_state
original_run_initial_state_actions original_run_initial_state_actions
end end
end end
def self.feed_options(user) def self.feed_options(user)
{ {
:title => 'Tracks Actions', :title => 'Tracks Actions',
:description => "Actions for #{user.display_name}" :description => "Actions for #{user.display_name}"
} }
end end
def starred? def starred?
tags.any? {|tag| tag.name == STARRED_TAG_NAME} tags.any? {|tag| tag.name == STARRED_TAG_NAME}
end end
def toggle_star! def toggle_star!
if starred? if starred?
delete_tags STARRED_TAG_NAME _remove_tags STARRED_TAG_NAME
tags.reload tags.reload
else else
add_tag STARRED_TAG_NAME _add_tags(STARRED_TAG_NAME)
tags.reload tags.reload
end end
starred? starred?
end end
def from_recurring_todo? def from_recurring_todo?
return self.recurring_todo_id != nil return self.recurring_todo_id != nil
end end
# Rich Todo API # Rich Todo API
def self.from_rich_message(user, default_context_id, description, notes) def self.from_rich_message(user, default_context_id, description, notes)
fields = description.match /([^>@]*)@?([^>]*)>?(.*)/ fields = description.match(/([^>@]*)@?([^>]*)>?(.*)/)
description = fields[1].strip description = fields[1].strip
context = fields[2].strip context = fields[2].strip
project = fields[3].strip project = fields[3].strip
context = nil if context == "" context = nil if context == ""
project = nil if project == "" project = nil if project == ""
context_id = default_context_id context_id = default_context_id
unless(context.nil?) unless(context.nil?)
found_context = user.active_contexts.find_by_namepart(context) found_context = user.active_contexts.find_by_namepart(context)
found_context = user.contexts.find_by_namepart(context) if found_context.nil? found_context = user.contexts.find_by_namepart(context) if found_context.nil?
context_id = found_context.id unless found_context.nil? context_id = found_context.id unless found_context.nil?
end end
unless user.contexts.exists? context_id unless user.contexts.exists? context_id
raise(CannotAccessContext, "Cannot access a context that does not belong to this user.") raise(CannotAccessContext, "Cannot access a context that does not belong to this user.")
end end
project_id = nil project_id = nil
unless(project.blank?) unless(project.blank?)
if(project[0..3].downcase == "new:") if(project[0..3].downcase == "new:")
found_project = user.projects.build found_project = user.projects.build
found_project.name = project[4..255+4].strip found_project.name = project[4..255+4].strip
found_project.save! found_project.save!
else else
found_project = user.active_projects.find_by_namepart(project) found_project = user.active_projects.find_by_namepart(project)
found_project = user.projects.find_by_namepart(project) if found_project.nil? found_project = user.projects.find_by_namepart(project) if found_project.nil?
end end
project_id = found_project.id unless found_project.nil? project_id = found_project.id unless found_project.nil?
end end
todo = user.todos.build todo = user.todos.build
todo.description = description todo.description = description
todo.notes = notes todo.notes = notes
todo.context_id = context_id todo.context_id = context_id
todo.project_id = project_id unless project_id.nil? todo.project_id = project_id unless project_id.nil?
return todo return todo
end end
end end

View file

@ -24,7 +24,21 @@
:html=> { :id=>'todo-form-new-action', :name=>'todo', :class => 'inline-form' }, :html=> { :id=>'todo-form-new-action', :name=>'todo', :class => 'inline-form' },
:before => "$('todo_new_action_submit').startWaiting()", :before => "$('todo_new_action_submit').startWaiting()",
:complete => "$('todo_new_action_submit').stopWaiting()", :complete => "$('todo_new_action_submit').stopWaiting()",
:condition => "!$('todo_new_action_submit').isWaiting()") do -%> :condition => "!$('todo_new_action_submit').isWaiting() && askIfNewContextProvided()") do -%>
<script type="text/javascript" charset="utf-8">
function askIfNewContextProvided() {
var contexts = new Array(<%= @contexts.map{|c| '\'' + c.name + '\''}.join(", ") %>);
var givenContextName = $('todo_context_name').value;
if (givenContextName.length == 0) return true; // show rails validation error
for (var i = 0; i < contexts.length; ++i) {
if (contexts[i] == givenContextName) {
return true;
}
}
return confirm('New context "' + givenContextName + '" will be also created. Are you sure?');
}
</script>
<div id="status"><%= error_messages_for("item", :object_name => 'action') %></div> <div id="status"><%= error_messages_for("item", :object_name => 'action') %></div>

View file

@ -14,12 +14,12 @@ end -%>
<% end -%> <% end -%>
<%= date_span -%> <%= link_to mobile_todo.description, formatted_todo_path(mobile_todo, :m) -%> <%= date_span -%> <%= link_to mobile_todo.description, formatted_todo_path(mobile_todo, :m) -%>
<% if parent_container_type == 'context' or parent_container_type == 'tag' -%> <% if parent_container_type == 'context' or parent_container_type == 'tag' -%>
<%= "<span class=prj> (" + <%= "<span class=\"prj\"> (" +
link_to(mobile_todo.project.name, formatted_project_path(mobile_todo.project, :m)) + link_to(mobile_todo.project.name, formatted_project_path(mobile_todo.project, :m)) +
")</span>" unless mobile_todo.project.nil? -%> ")</span>" unless mobile_todo.project.nil? -%>
<% end <% end
if parent_container_type == 'project' or parent_container_type == 'tag' -%> if parent_container_type == 'project' or parent_container_type == 'tag' -%>
<%= "<span class=ctx> (" + <%= "<span class=\"ctx\"> (" +
link_to(mobile_todo.context.name, formatted_context_path(mobile_todo.context, :m)) + link_to(mobile_todo.context.name, formatted_context_path(mobile_todo.context, :m)) +
")</span>" -%> ")</span>" -%>
<% end -%> <% end -%>

View file

@ -1,109 +1,111 @@
# Be sure to restart your webserver when you modify this file. # Be sure to restart your webserver when you modify this file.
# Uncomment below to force Rails into production mode # Uncomment below to force Rails into production mode
# (Use only when you can't set environment variables through your web/app server) # (Use only when you can't set environment variables through your web/app server)
# ENV['RAILS_ENV'] = 'production' # ENV['RAILS_ENV'] = 'production'
# Bootstrap the Rails environment, frameworks, and default configuration # Bootstrap the Rails environment, frameworks, and default configuration
require File.join(File.dirname(__FILE__), 'boot') require File.join(File.dirname(__FILE__), 'boot')
# This is the 'salt' to add to the password before it is encrypted # This is the 'salt' to add to the password before it is encrypted
# You need to change this to something unique for yourself # You need to change this to something unique for yourself
SALT = "change-me" SALT = "change-me"
class Rails::Configuration class Rails::Configuration
attr_accessor :action_web_service attr_accessor :action_web_service
end end
# Leave this alone or set it to one or more of ['database', 'ldap', 'open_id']. # Leave this alone or set it to one or more of ['database', 'ldap', 'open_id'].
# If you choose ldap, see the additional configuration options further down. # If you choose ldap, see the additional configuration options further down.
AUTHENTICATION_SCHEMES = ['database'] AUTHENTICATION_SCHEMES = ['database']
Rails::Initializer.run do |config| Rails::Initializer.run do |config|
# Skip frameworks you're not going to use # Skip frameworks you're not going to use
# config.frameworks -= [ :action_web_service, :action_mailer ] # config.frameworks -= [ :action_web_service, :action_mailer ]
config.frameworks += [ :action_web_service] config.frameworks += [ :action_web_service]
config.action_web_service = Rails::OrderedOptions.new config.action_web_service = Rails::OrderedOptions.new
config.load_paths += %W( #{RAILS_ROOT}/app/apis ) config.load_paths += %W( #{RAILS_ROOT}/app/apis )
config.action_controller.use_accept_header = true config.gem "highline"
# Add additional load paths for your own custom dirs config.action_controller.use_accept_header = true
# config.load_paths += %W( #{RAILS_ROOT}/app/services )
# Add additional load paths for your own custom dirs
# Force all environments to use the same logger level # config.load_paths += %W( #{RAILS_ROOT}/app/services )
# (by default production uses :info, the others :debug)
# config.log_level = :debug # Force all environments to use the same logger level
# (by default production uses :info, the others :debug)
# Use the database for sessions instead of the file system # config.log_level = :debug
# (create the session table with 'rake create_sessions_table')
config.action_controller.session_store = :active_record_store # Use the database for sessions instead of the file system
# (create the session table with 'rake create_sessions_table')
config.action_controller.session = { config.action_controller.session_store = :active_record_store
:session_key => '_tracks_session_id',
:secret => SALT * (30.0 / SALT.length).ceil #must be at least 30 characters config.action_controller.session = {
} :session_key => '_tracks_session_id',
:secret => SALT * (30.0 / SALT.length).ceil #must be at least 30 characters
# Enable page/fragment caching by setting a file-based store }
# (remember to create the caching directory and make it readable to the application)
# config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache" # Enable page/fragment caching by setting a file-based store
# (remember to create the caching directory and make it readable to the application)
# Activate observers that should always be running # config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache"
# config.active_record.observers = :cacher, :garbage_collector
# Activate observers that should always be running
# Make Active Record use UTC-base instead of local time # config.active_record.observers = :cacher, :garbage_collector
config.active_record.default_timezone = :utc
# Make Active Record use UTC-base instead of local time
# You''ll probably want to change this to the time zone of the computer where Tracks is running config.active_record.default_timezone = :utc
# run rake time:zones:local have Rails suggest time zone names on your system
config.time_zone = 'UTC' # You''ll probably want to change this to the time zone of the computer where Tracks is running
# run rake time:zones:local have Rails suggest time zone names on your system
# Use Active Record's schema dumper instead of SQL when creating the test database config.time_zone = 'UTC'
# (enables use of different database adapters for development and test environments)
config.active_record.schema_format = :ruby # Use Active Record's schema dumper instead of SQL when creating the test database
# (enables use of different database adapters for development and test environments)
# See Rails::Configuration for more options config.active_record.schema_format = :ruby
end
# See Rails::Configuration for more options
# Add new inflection rules using the following format end
# (all these examples are active by default):
# Inflector.inflections do |inflect| # Add new inflection rules using the following format
# inflect.plural /^(ox)$/i, '\1en' # (all these examples are active by default):
# inflect.singular /^(ox)en/i, '\1' # Inflector.inflections do |inflect|
# inflect.irregular 'person', 'people' # inflect.plural /^(ox)$/i, '\1en'
# inflect.uncountable %w( fish sheep ) # inflect.singular /^(ox)en/i, '\1'
# end # inflect.irregular 'person', 'people'
# inflect.uncountable %w( fish sheep )
# Include your application configuration below # end
# Include your application configuration below
require 'name_part_finder'
require 'tracks/todo_list'
require 'tracks/config' require 'name_part_finder'
require 'activerecord_base_tag_extensions' # Needed for tagging-specific extensions require 'tracks/todo_list'
require 'digest/sha1' #Needed to support 'rake db:fixtures:load' on some ruby installs: http://dev.rousette.org.uk/ticket/557 require 'tracks/config'
require 'prototype_helper_extensions' require 'tagging_extensions' # Needed for tagging-specific extensions
require 'digest/sha1' #Needed to support 'rake db:fixtures:load' on some ruby installs: http://dev.rousette.org.uk/ticket/557
if (AUTHENTICATION_SCHEMES.include? 'ldap') require 'prototype_helper_extensions'
require 'net/ldap' #requires ruby-net-ldap gem be installed
require 'simple_ldap_authenticator' if (AUTHENTICATION_SCHEMES.include? 'ldap')
SimpleLdapAuthenticator.ldap_library = 'net/ldap' require 'net/ldap' #requires ruby-net-ldap gem be installed
SimpleLdapAuthenticator.servers = %w'localhost' require 'simple_ldap_authenticator'
SimpleLdapAuthenticator.use_ssl = false SimpleLdapAuthenticator.ldap_library = 'net/ldap'
SimpleLdapAuthenticator.login_format = 'cn=%s,dc=example,dc=com' SimpleLdapAuthenticator.servers = %w'localhost'
end SimpleLdapAuthenticator.use_ssl = false
if (AUTHENTICATION_SCHEMES.include? 'open_id') SimpleLdapAuthenticator.login_format = 'cn=%s,dc=example,dc=com'
#requires ruby-openid gem to be installed end
end if (AUTHENTICATION_SCHEMES.include? 'open_id')
#requires ruby-openid gem to be installed
# setting this to true will make the cookies only available over HTTPS end
TRACKS_COOKIES_SECURE = false
# setting this to true will make the cookies only available over HTTPS
tracks_version='1.7RC' TRACKS_COOKIES_SECURE = false
# comment out next two lines if you do not want (or can not) the date of the tracks_version='1.7RC'
# last git commit in the footer
# info=`git log --pretty=format:"%ai" -1` # comment out next two lines if you do not want (or can not) the date of the
# tracks_version=tracks_version + ' ('+info+')' # last git commit in the footer
# info=`git log --pretty=format:"%ai" -1`
TRACKS_VERSION=tracks_version # tracks_version=tracks_version + ' ('+info+')'
TRACKS_VERSION=tracks_version

View file

@ -1,30 +0,0 @@
class ActiveRecord::Base
# These methods will work for any model instances
# Tag with deletes all the current tags before adding the new ones
# This makes the edit form more intiuitive:
# Whatever is in the tags text field is what gets set as the tags for that action
# If you submit an empty tags text field, all the tags are removed.
def tag_with(tags, user)
Tag.transaction do
Tagging.delete_all("taggable_id = #{self.id} and taggable_type = '#{self.class}' and user_id = #{user.id}")
tags.downcase.split(",").each do |tag|
Tag.find_or_create_by_name(tag.strip).on(self, user)
end
end
end
def tag_list
tags.map(&:name).join(', ')
end
def delete_tags tag_string
split = tag_string.downcase.split(",")
tags.delete tags.select{|t| split.include? t.name.strip}
end
def add_tag tag_name
Tag.find_or_create_by_name(tag_name.strip).on(self,user)
end
end

148
lib/tagging_extensions.rb Normal file
View file

@ -0,0 +1,148 @@
class ActiveRecord::Base #:nodoc:
# These extensions make models taggable. This file is automatically generated and required by your app if you run the tagging generator included with has_many_polymorphs.
module TaggingExtensions
# Add tags to <tt>self</tt>. Accepts a string of tagnames, an array of tagnames, an array of ids, or an array of Tags.
#
# We need to avoid name conflicts with the built-in ActiveRecord association methods, thus the underscores.
def _add_tags incoming
taggable?(true)
tag_cast_to_string(incoming).each do |tag_name|
begin
tag = Tag.find_or_create_by_name(tag_name)
raise Tag::Error, "tag could not be saved: #{tag_name}" if tag.new_record?
tag.taggables << self
rescue ActiveRecord::StatementInvalid => e
raise unless e.to_s =~ /duplicate/i
end
end
end
# Removes tags from <tt>self</tt>. Accepts a string of tagnames, an array of tagnames, an array of ids, or an array of Tags.
def _remove_tags outgoing
taggable?(true)
outgoing = tag_cast_to_string(outgoing)
tags.delete(*(tags.select do |tag|
outgoing.include? tag.name
end))
end
# Returns the tags on <tt>self</tt> as a string.
def tag_list
# Redefined later to avoid an RDoc parse error.
end
# Replace the existing tags on <tt>self</tt>. Accepts a string of tagnames, an array of tagnames, an array of ids, or an array of Tags.
def tag_with list
#:stopdoc:
taggable?(true)
list = tag_cast_to_string(list)
# Transactions may not be ideal for you here; be aware.
Tag.transaction do
current = tags.map(&:name)
_add_tags(list - current)
_remove_tags(current - list)
end
self
#:startdoc:
end
# Returns the tags on <tt>self</tt> as a string.
def tag_list #:nodoc:
#:stopdoc:
taggable?(true)
tags.reload
tags.to_s
#:startdoc:
end
private
def tag_cast_to_string obj #:nodoc:
case obj
when Array
obj.map! do |item|
case item
# removed next line: its prevents adding numbers as tags
# when /^\d+$/, Fixnum then Tag.find(item).name # This will be slow if you use ids a lot.
when Tag then item.name
when String then item
else
raise "Invalid type"
end
end
when String
obj = obj.split(Tag::DELIMITER).map do |tag_name|
tag_name.strip.squeeze(" ")
end
else
raise "Invalid object of class #{obj.class} as tagging method parameter"
end.flatten.compact.map(&:downcase).uniq
end
# Check if a model is in the :taggables target list. The alternative to this check is to explicitly include a TaggingMethods module (which you would create) in each target model.
def taggable?(should_raise = false) #:nodoc:
unless flag = respond_to?(:tags)
raise "#{self.class} is not a taggable model" if should_raise
end
flag
end
end
module TaggingFinders
#
# Find all the objects tagged with the supplied list of tags
#
# Usage : Model.tagged_with("ruby")
# Model.tagged_with("hello", "world")
# Model.tagged_with("hello", "world", :limit => 10)
#
def tagged_with(*tag_list)
options = tag_list.last.is_a?(Hash) ? tag_list.pop : {}
tag_list = parse_tags(tag_list)
scope = scope(:find)
options[:select] ||= "#{table_name}.*"
options[:from] ||= "#{table_name}, tags, taggings"
sql = "SELECT #{(scope && scope[:select]) || options[:select]} "
sql << "FROM #{(scope && scope[:from]) || options[:from]} "
add_joins!(sql, options, scope)
sql << "WHERE #{table_name}.#{primary_key} = taggings.taggable_id "
sql << "AND taggings.taggable_type = '#{ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s}' "
sql << "AND taggings.tag_id = tags.id "
tag_list_condition = tag_list.map {|t| "'#{t}'"}.join(", ")
sql << "AND (tags.name IN (#{sanitize_sql(tag_list_condition)})) "
sql << "AND #{sanitize_sql(options[:conditions])} " if options[:conditions]
sql << "GROUP BY #{table_name}.id "
sql << "HAVING COUNT(taggings.tag_id) = #{tag_list.size}"
add_order!(sql, options[:order], scope)
add_limit!(sql, options, scope)
add_lock!(sql, options, scope)
find_by_sql(sql)
end
def parse_tags(tags)
return [] if tags.blank?
tags = Array(tags).first
tags = tags.respond_to?(:flatten) ? tags.flatten : tags.split(Tag::DELIMITER)
tags.map { |tag| tag.strip.squeeze(" ") }.flatten.compact.map(&:downcase).uniq
end
end
include TaggingExtensions
extend TaggingFinders
end

View file

@ -1,259 +1,259 @@
require File.dirname(__FILE__) + '/../test_helper' require File.dirname(__FILE__) + '/../test_helper'
require File.dirname(__FILE__) + '/todo_container_controller_test_base' require File.dirname(__FILE__) + '/todo_container_controller_test_base'
require 'projects_controller' require 'projects_controller'
# Re-raise errors caught by the controller. # Re-raise errors caught by the controller.
class ProjectsController; def rescue_action(e) raise e end; end class ProjectsController; def rescue_action(e) raise e end; end
class ProjectsControllerTest < TodoContainerControllerTestBase class ProjectsControllerTest < TodoContainerControllerTestBase
fixtures :users, :todos, :preferences, :projects, :contexts fixtures :users, :todos, :preferences, :projects, :contexts
def setup def setup
perform_setup(Project, ProjectsController) perform_setup(Project, ProjectsController)
end end
def test_projects_list def test_projects_list
login_as :admin_user login_as :admin_user
get :index get :index
end end
def test_show_exposes_deferred_todos def test_show_exposes_deferred_todos
p = projects(:timemachine) p = projects(:timemachine)
login_as :admin_user login_as :admin_user
get :show, :id => p.to_param get :show, :id => p.to_param
assert_not_nil assigns['deferred'] assert_not_nil assigns['deferred']
assert_equal 1, assigns['deferred'].size assert_equal 1, assigns['deferred'].size
t = p.not_done_todos[0] t = p.not_done_todos[0]
t.show_from = 1.days.from_now.utc t.show_from = 1.days.from_now.utc
t.save! t.save!
get :show, :id => p.to_param get :show, :id => p.to_param
assert_equal 2, assigns['deferred'].size assert_equal 2, assigns['deferred'].size
end end
def test_show_exposes_next_project_in_same_state def test_show_exposes_next_project_in_same_state
login_as :admin_user login_as :admin_user
get :show, :id => projects(:timemachine).to_param get :show, :id => projects(:timemachine).to_param
assert_equal(projects(:moremoney), assigns['next_project']) assert_equal(projects(:moremoney), assigns['next_project'])
end end
def test_show_exposes_previous_project_in_same_state def test_show_exposes_previous_project_in_same_state
login_as :admin_user login_as :admin_user
get :show, :id => projects(:moremoney).to_param get :show, :id => projects(:moremoney).to_param
assert_equal(projects(:timemachine), assigns['previous_project']) assert_equal(projects(:timemachine), assigns['previous_project'])
end end
def test_create_project_via_ajax_increments_number_of_projects def test_create_project_via_ajax_increments_number_of_projects
assert_ajax_create_increments_count 'My New Project' assert_ajax_create_increments_count 'My New Project'
end end
def test_create_project_with_ajax_success_rjs def test_create_project_with_ajax_success_rjs
ajax_create 'My New Project' ajax_create 'My New Project'
assert_rjs :insert_html, :bottom, "list-active-projects" assert_rjs :insert_html, :bottom, "list-active-projects"
assert_rjs :sortable, 'list-active-projects', { :tag => 'div', :handle => 'handle', :complete => visual_effect(:highlight, 'list-active-projects'), :url => order_projects_path } assert_rjs :sortable, 'list-active-projects', { :tag => 'div', :handle => 'handle', :complete => visual_effect(:highlight, 'list-active-projects'), :url => order_projects_path }
# not yet sure how to write the following properly... # not yet sure how to write the following properly...
assert_rjs :call, "Form.reset", "project-form" assert_rjs :call, "Form.reset", "project-form"
assert_rjs :call, "Form.focusFirstElement", "project-form" assert_rjs :call, "Form.focusFirstElement", "project-form"
end end
def test_create_project_and_go_to_project_page def test_create_project_and_go_to_project_page
num_projects = Project.count num_projects = Project.count
xhr :post, :create, { :project => {:name => 'Immediate Project Planning Required'}, :go_to_project => 1} xhr :post, :create, { :project => {:name => 'Immediate Project Planning Required'}, :go_to_project => 1}
assert_js_redirected_to %r{/?projects/\d+} assert_js_redirected_to %r{/?projects/\d+}
assert_equal num_projects + 1, Project.count assert_equal num_projects + 1, Project.count
end end
def test_create_with_comma_in_name_does_not_increment_number_of_projects def test_create_with_comma_in_name_does_not_increment_number_of_projects
assert_ajax_create_does_not_increment_count 'foo,bar' assert_ajax_create_does_not_increment_count 'foo,bar'
end end
def test_create_with_comma_in_name_fails_with_rjs def test_create_with_comma_in_name_fails_with_rjs
ajax_create 'foo,bar' ajax_create 'foo,bar'
assert_rjs :show, 'status' assert_rjs :show, 'status'
# Not working with Rails 2.0 upgrade # Not working with Rails 2.0 upgrade
# assert_rjs :update, 'status', "<div class=\"ErrorExplanation\" id=\"ErrorExplanation\"><h2>1 error prohibited this record from being saved</h2><p>There were problems with the following fields:</p><ul>Name cannot contain the comma (',') character</ul></div>" # assert_rjs :update, 'status', "<div class=\"ErrorExplanation\" id=\"ErrorExplanation\"><h2>1 error prohibited this record from being saved</h2><p>There were problems with the following fields:</p><ul>Name cannot contain the comma (',') character</ul></div>"
end end
def test_todo_state_is_project_hidden_after_hiding_project def test_todo_state_is_project_hidden_after_hiding_project
p = projects(:timemachine) p = projects(:timemachine)
todos = p.todos.find_in_state(:all, :active) todos = p.todos.find_in_state(:all, :active)
login_as(:admin_user) login_as(:admin_user)
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"=>"hidden"}
todos.each do |t| todos.each do |t|
assert_equal :project_hidden, t.reload().current_state assert_equal :project_hidden, t.reload().current_state
end end
assert p.reload().hidden? assert p.reload().hidden?
end end
def test_not_done_counts_after_hiding_and_unhiding_project def test_not_done_counts_after_hiding_and_unhiding_project
p = projects(:timemachine) p = projects(:timemachine)
todos = p.todos.find_in_state(:all, :active) todos = p.todos.find_in_state(:all, :active)
login_as(:admin_user) login_as(:admin_user)
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"=>"hidden"}
xhr :post, :update, :id => 1, "project"=>{"name"=>p.name, "description"=>p.description, "state"=>"active"} xhr :post, :update, :id => 1, "project"=>{"name"=>p.name, "description"=>p.description, "state"=>"active"}
todos.each do |t| todos.each do |t|
assert_equal :active, t.reload().current_state assert_equal :active, t.reload().current_state
end end
assert p.reload().active? assert p.reload().active?
end end
def test_rss_feed_content def test_rss_feed_content
login_as(:admin_user) login_as(:admin_user)
get :index, { :format => "rss" } get :index, { :format => "rss" }
assert_equal 'application/rss+xml', @response.content_type assert_equal 'application/rss+xml', @response.content_type
#puts @response.body #puts @response.body
assert_xml_select 'rss[version="2.0"]' do assert_xml_select 'rss[version="2.0"]' do
assert_select 'channel' do assert_select 'channel' do
assert_select '>title', 'Tracks Projects' assert_select '>title', 'Tracks Projects'
assert_select '>description', "Lists all the projects for #{users(:admin_user).display_name}" assert_select '>description', "Lists all the projects for #{users(:admin_user).display_name}"
assert_select 'language', 'en-us' assert_select 'language', 'en-us'
assert_select 'ttl', '40' assert_select 'ttl', '40'
end end
assert_select 'item', 3 do assert_select 'item', 3 do
assert_select 'title', /.+/ assert_select 'title', /.+/
assert_select 'description' do assert_select 'description' do
assert_select_encoded do assert_select_encoded do
assert_select 'p', /^\d+&nbsp;actions\. Project is (active|hidden|completed)\.$/ assert_select 'p', /^\d+&nbsp;actions\. Project is (active|hidden|completed)\.$/
end end
end end
%w(guid link).each do |node| %w(guid link).each do |node|
assert_select node, /http:\/\/test.host\/projects\/.+/ assert_select node, /http:\/\/test.host\/projects\/.+/
end end
assert_select 'pubDate', projects(:timemachine).updated_at.to_s(:rfc822) assert_select 'pubDate', projects(:timemachine).updated_at.to_s(:rfc822)
end end
end end
end end
def test_rss_feed_not_accessible_to_anonymous_user_without_token def test_rss_feed_not_accessible_to_anonymous_user_without_token
login_as nil login_as nil
get :index, { :format => "rss" } get :index, { :format => "rss" }
assert_response 401 assert_response 401
end end
def test_rss_feed_not_accessible_to_anonymous_user_with_invalid_token def test_rss_feed_not_accessible_to_anonymous_user_with_invalid_token
login_as nil login_as nil
get :index, { :format => "rss", :token => 'foo' } get :index, { :format => "rss", :token => 'foo' }
assert_response 401 assert_response 401
end end
def test_rss_feed_accessible_to_anonymous_user_with_valid_token def test_rss_feed_accessible_to_anonymous_user_with_valid_token
login_as nil login_as nil
get :index, { :format => "rss", :token => users(:admin_user).token } get :index, { :format => "rss", :token => users(:admin_user).token }
assert_response :ok assert_response :ok
end end
def test_atom_feed_content def test_atom_feed_content
login_as :admin_user login_as :admin_user
get :index, { :format => "atom" } get :index, { :format => "atom" }
assert_equal 'application/atom+xml', @response.content_type assert_equal 'application/atom+xml', @response.content_type
#puts @response.body #puts @response.body
assert_xml_select 'feed[xmlns="http://www.w3.org/2005/Atom"]' do assert_xml_select 'feed[xmlns="http://www.w3.org/2005/Atom"]' do
assert_select '>title', 'Tracks Projects' assert_select '>title', 'Tracks Projects'
assert_select '>subtitle', "Lists all the projects for #{users(:admin_user).display_name}" assert_select '>subtitle', "Lists all the projects for #{users(:admin_user).display_name}"
assert_select 'entry', 3 do assert_select 'entry', 3 do
assert_select 'title', /.+/ assert_select 'title', /.+/
assert_select 'content[type="html"]' do assert_select 'content[type="html"]' do
assert_select_encoded do assert_select_encoded do
assert_select 'p', /\d+&nbsp;actions. Project is (active|hidden|completed)./ assert_select 'p', /\d+&nbsp;actions. Project is (active|hidden|completed)./
end end
end end
assert_select 'published', /(#{Regexp.escape(projects(:timemachine).updated_at.xmlschema)}|#{Regexp.escape(projects(:moremoney).updated_at.xmlschema)})/ assert_select 'published', /(#{Regexp.escape(projects(:timemachine).updated_at.xmlschema)}|#{Regexp.escape(projects(:moremoney).updated_at.xmlschema)})/
end end
end end
end end
def test_atom_feed_not_accessible_to_anonymous_user_without_token def test_atom_feed_not_accessible_to_anonymous_user_without_token
login_as nil login_as nil
get :index, { :format => "atom" } get :index, { :format => "atom" }
assert_response 401 assert_response 401
end end
def test_atom_feed_not_accessible_to_anonymous_user_with_invalid_token def test_atom_feed_not_accessible_to_anonymous_user_with_invalid_token
login_as nil login_as nil
get :index, { :format => "atom", :token => 'foo' } get :index, { :format => "atom", :token => 'foo' }
assert_response 401 assert_response 401
end end
def test_atom_feed_accessible_to_anonymous_user_with_valid_token def test_atom_feed_accessible_to_anonymous_user_with_valid_token
login_as nil login_as nil
get :index, { :format => "atom", :token => users(:admin_user).token } get :index, { :format => "atom", :token => users(:admin_user).token }
assert_response :ok assert_response :ok
end end
def test_text_feed_content def test_text_feed_content
login_as :admin_user login_as :admin_user
get :index, { :format => "txt" } get :index, { :format => "txt" }
assert_equal 'text/plain', @response.content_type assert_equal 'text/plain', @response.content_type
assert !(/&nbsp;/.match(@response.body)) assert !(/&nbsp;/.match(@response.body))
#puts @response.body #puts @response.body
end end
def test_text_feed_content_for_projects_with_no_actions def test_text_feed_content_for_projects_with_no_actions
login_as :admin_user login_as :admin_user
p = projects(:timemachine) p = projects(:timemachine)
p.todos.each { |t| t.destroy } p.todos.each { |t| t.destroy }
get :index, { :format => "txt", :only_active_with_no_next_actions => true } get :index, { :format => "txt", :only_active_with_no_next_actions => true }
assert (/^\s*BUILD A WORKING TIME MACHINE\s+0 actions. Project is active.\s*$/.match(@response.body)) assert (/^\s*BUILD A WORKING TIME MACHINE\s+0 actions. Project is active.\s*$/.match(@response.body))
assert !(/[1-9] actions/.match(@response.body)) assert !(/[1-9] actions/.match(@response.body))
end end
def test_text_feed_not_accessible_to_anonymous_user_without_token def test_text_feed_not_accessible_to_anonymous_user_without_token
login_as nil login_as nil
get :index, { :format => "txt" } get :index, { :format => "txt" }
assert_response 401 assert_response 401
end end
def test_text_feed_not_accessible_to_anonymous_user_with_invalid_token def test_text_feed_not_accessible_to_anonymous_user_with_invalid_token
login_as nil login_as nil
get :index, { :format => "txt", :token => 'foo' } get :index, { :format => "txt", :token => 'foo' }
assert_response 401 assert_response 401
end end
def test_text_feed_accessible_to_anonymous_user_with_valid_token def test_text_feed_accessible_to_anonymous_user_with_valid_token
login_as nil login_as nil
get :index, { :format => "txt", :token => users(:admin_user).token } get :index, { :format => "txt", :token => users(:admin_user).token }
assert_response :ok assert_response :ok
end end
def test_actionize_sorts_active_projects_by_number_of_tasks def test_actionize_sorts_active_projects_by_number_of_tasks
login_as :admin_user login_as :admin_user
u = users(:admin_user) u = users(:admin_user)
post :actionize, :state => "active", :format => 'js' post :actionize, :state => "active", :format => 'js'
assert_equal 2, projects(:gardenclean).position assert_equal 1, projects(:gardenclean).position
assert_equal 1, projects(:moremoney).position assert_equal 2, projects(:moremoney).position
assert_equal 3, projects(:timemachine).position assert_equal 3, projects(:timemachine).position
end end
def test_alphabetize_sorts_active_projects_alphabetically def test_alphabetize_sorts_active_projects_alphabetically
login_as :admin_user login_as :admin_user
u = users(:admin_user) u = users(:admin_user)
post :alphabetize, :state => "active", :format => 'js' post :alphabetize, :state => "active", :format => 'js'
assert_equal 1, projects(:timemachine).position assert_equal 1, projects(:timemachine).position
assert_equal 2, projects(:gardenclean).position assert_equal 2, projects(:gardenclean).position
assert_equal 3, projects(:moremoney).position assert_equal 3, projects(:moremoney).position
end end
def test_alphabetize_assigns_state def test_alphabetize_assigns_state
login_as :admin_user login_as :admin_user
post :alphabetize, :state => "active", :format => 'js' post :alphabetize, :state => "active", :format => 'js'
assert_equal "active", assigns['state'] assert_equal "active", assigns['state']
end end
def test_alphabetize_assigns_projects def test_alphabetize_assigns_projects
login_as :admin_user login_as :admin_user
post :alphabetize, :state => "active", :format => 'js' post :alphabetize, :state => "active", :format => 'js'
exposed_projects = assigns['projects'] exposed_projects = assigns['projects']
assert_equal 3, exposed_projects.length assert_equal 3, exposed_projects.length
assert_equal projects(:timemachine), exposed_projects[0] assert_equal projects(:timemachine), exposed_projects[0]
assert_equal projects(:gardenclean), exposed_projects[1] assert_equal projects(:gardenclean), exposed_projects[1]
assert_equal projects(:moremoney), exposed_projects[2] assert_equal projects(:moremoney), exposed_projects[2]
end end
def protect_against_forgery? def protect_against_forgery?
false false
end end
end end

View file

@ -1,488 +1,488 @@
require File.dirname(__FILE__) + '/../test_helper' require File.dirname(__FILE__) + '/../test_helper'
require 'todos_controller' require 'todos_controller'
# Re-raise errors caught by the controller. # Re-raise errors caught by the controller.
class TodosController; def rescue_action(e) raise e end; end class TodosController; def rescue_action(e) raise e end; end
class TodosControllerTest < Test::Rails::TestCase class TodosControllerTest < Test::Rails::TestCase
fixtures :users, :preferences, :projects, :contexts, :todos, :tags, :taggings, :recurring_todos fixtures :users, :preferences, :projects, :contexts, :todos, :tags, :taggings, :recurring_todos
def setup def setup
@controller = TodosController.new @controller = TodosController.new
@request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
end end
def test_get_index_when_not_logged_in def test_get_index_when_not_logged_in
get :index get :index
assert_redirected_to :controller => 'login', :action => 'login' assert_redirected_to :controller => 'login', :action => 'login'
end end
def test_not_done_counts def test_not_done_counts
login_as(:admin_user) login_as(:admin_user)
get :index get :index
assert_equal 2, assigns['project_not_done_counts'][projects(:timemachine).id] assert_equal 2, assigns['project_not_done_counts'][projects(:timemachine).id]
assert_equal 3, assigns['context_not_done_counts'][contexts(:call).id] assert_equal 3, assigns['context_not_done_counts'][contexts(:call).id]
assert_equal 1, assigns['context_not_done_counts'][contexts(:lab).id] assert_equal 1, assigns['context_not_done_counts'][contexts(:lab).id]
end end
def test_tag_is_retrieved_properly def test_tag_is_retrieved_properly
login_as(:admin_user) login_as(:admin_user)
get :index get :index
t = assigns['not_done_todos'].find{|t| t.id == 2} t = assigns['not_done_todos'].find{|t| t.id == 2}
assert_equal 1, t.tags.count assert_equal 1, t.tags.count
assert_equal 'foo', t.tags[0].name assert_equal 'foo', t.tags[0].name
assert !t.starred? assert !t.starred?
end end
def test_not_done_counts_after_hiding_project def test_not_done_counts_after_hiding_project
p = Project.find(1) p = Project.find(1)
p.hide! p.hide!
p.save! p.save!
login_as(:admin_user) login_as(:admin_user)
get :index get :index
assert_equal nil, assigns['project_not_done_counts'][projects(:timemachine).id] assert_equal nil, assigns['project_not_done_counts'][projects(:timemachine).id]
assert_equal 2, assigns['context_not_done_counts'][contexts(:call).id] assert_equal 2, assigns['context_not_done_counts'][contexts(:call).id]
assert_equal nil, assigns['context_not_done_counts'][contexts(:lab).id] assert_equal nil, assigns['context_not_done_counts'][contexts(:lab).id]
end end
def test_not_done_counts_after_hiding_and_unhiding_project def test_not_done_counts_after_hiding_and_unhiding_project
p = Project.find(1) p = Project.find(1)
p.hide! p.hide!
p.save! p.save!
p.activate! p.activate!
p.save! p.save!
login_as(:admin_user) login_as(:admin_user)
get :index get :index
assert_equal 2, assigns['project_not_done_counts'][projects(:timemachine).id] assert_equal 2, assigns['project_not_done_counts'][projects(:timemachine).id]
assert_equal 3, assigns['context_not_done_counts'][contexts(:call).id] assert_equal 3, assigns['context_not_done_counts'][contexts(:call).id]
assert_equal 1, assigns['context_not_done_counts'][contexts(:lab).id] assert_equal 1, assigns['context_not_done_counts'][contexts(:lab).id]
end end
def test_deferred_count_for_project_source_view def test_deferred_count_for_project_source_view
login_as(:admin_user) login_as(:admin_user)
xhr :post, :toggle_check, :id => 5, :_source_view => 'project' xhr :post, :toggle_check, :id => 5, :_source_view => 'project'
assert_equal 1, assigns['deferred_count'] assert_equal 1, assigns['deferred_count']
xhr :post, :toggle_check, :id => 15, :_source_view => 'project' xhr :post, :toggle_check, :id => 15, :_source_view => 'project'
assert_equal 0, assigns['deferred_count'] assert_equal 0, assigns['deferred_count']
end end
def test_destroy_todo def test_destroy_todo
login_as(:admin_user) login_as(:admin_user)
xhr :post, :destroy, :id => 1, :_source_view => 'todo' xhr :post, :destroy, :id => 1, :_source_view => 'todo'
assert_rjs :page, "todo_1", :remove assert_rjs :page, "todo_1", :remove
# #assert_rjs :replace_html, "badge-count", '9' # #assert_rjs :replace_html, "badge-count", '9'
end end
def test_create_todo def test_create_todo
assert_difference Todo, :count do assert_difference Todo, :count do
login_as(:admin_user) login_as(:admin_user)
put :create, :_source_view => 'todo', "context_name"=>"library", "project_name"=>"Build a working time machine", "todo"=>{"notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo bar" put :create, :_source_view => 'todo', "context_name"=>"library", "project_name"=>"Build a working time machine", "todo"=>{"notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo bar"
end end
end end
def test_create_todo_via_xml def test_create_todo_via_xml
login_as(:admin_user) login_as(:admin_user)
assert_difference Todo, :count do assert_difference Todo, :count do
put :create, :format => "xml", "request" => { "context_name"=>"library", "project_name"=>"Build a working time machine", "todo"=>{"notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo bar" } put :create, :format => "xml", "request" => { "context_name"=>"library", "project_name"=>"Build a working time machine", "todo"=>{"notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo bar" }
assert_response 201 assert_response 201
end end
end end
def test_fail_to_create_todo_via_xml def test_fail_to_create_todo_via_xml
login_as(:admin_user) login_as(:admin_user)
# #try to create with no context, which is not valid # #try to create with no context, which is not valid
put :create, :format => "xml", "request" => { "project_name"=>"Build a working time machine", "todo"=>{"notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo bar" } put :create, :format => "xml", "request" => { "project_name"=>"Build a working time machine", "todo"=>{"notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo bar" }
assert_response 422 assert_response 422
assert_xml_select "errors" do assert_xml_select "errors" do
assert_xml_select "error", "Context can't be blank" assert_xml_select "error", "Context can't be blank"
end end
end end
def test_create_deferred_todo def test_create_deferred_todo
original_todo_count = Todo.count original_todo_count = Todo.count
login_as(:admin_user) login_as(:admin_user)
put :create, :_source_view => 'todo', "context_name"=>"library", "project_name"=>"Build a working time machine", "todo"=>{"notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2026", 'show_from' => '30/10/2026'}, "tag_list"=>"foo bar" put :create, :_source_view => 'todo', "context_name"=>"library", "project_name"=>"Build a working time machine", "todo"=>{"notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2026", 'show_from' => '30/10/2026'}, "tag_list"=>"foo bar"
assert_equal original_todo_count + 1, Todo.count assert_equal original_todo_count + 1, Todo.count
end end
def test_update_todo_project def test_update_todo_project
t = Todo.find(1) t = Todo.find(1)
login_as(:admin_user) login_as(:admin_user)
xhr :post, :update, :id => 1, :_source_view => 'todo', "context_name"=>"library", "project_name"=>"Build a working time machine", "todo"=>{"id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo bar" xhr :post, :update, :id => 1, :_source_view => 'todo', "context_name"=>"library", "project_name"=>"Build a working time machine", "todo"=>{"id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo bar"
t = Todo.find(1) t = Todo.find(1)
assert_equal 1, t.project_id assert_equal 1, t.project_id
end end
def test_update_todo_project_to_none def test_update_todo_project_to_none
t = Todo.find(1) t = Todo.find(1)
login_as(:admin_user) login_as(:admin_user)
xhr :post, :update, :id => 1, :_source_view => 'todo', "context_name"=>"library", "project_name"=>"None", "todo"=>{"id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo bar" xhr :post, :update, :id => 1, :_source_view => 'todo', "context_name"=>"library", "project_name"=>"None", "todo"=>{"id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo bar"
t = Todo.find(1) t = Todo.find(1)
assert_nil t.project_id assert_nil t.project_id
end end
def test_update_todo_to_deferred_is_reflected_in_badge_count def test_update_todo_to_deferred_is_reflected_in_badge_count
login_as(:admin_user) login_as(:admin_user)
get :index get :index
assert_equal 11, assigns['count'] assert_equal 11, assigns['count']
xhr :post, :update, :id => 1, :_source_view => 'todo', "context_name"=>"library", "project_name"=>"Make more money than Billy Gates", "todo"=>{"id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006", "show_from"=>"30/11/2030"}, "tag_list"=>"foo bar" xhr :post, :update, :id => 1, :_source_view => 'todo', "context_name"=>"library", "project_name"=>"Make more money than Billy Gates", "todo"=>{"id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006", "show_from"=>"30/11/2030"}, "tag_list"=>"foo bar"
assert_equal 10, assigns['down_count'] assert_equal 10, assigns['down_count']
end end
def test_update_todo def test_update_todo
t = Todo.find(1) t = Todo.find(1)
login_as(:admin_user) login_as(:admin_user)
xhr :post, :update, :id => 1, :_source_view => 'todo', "todo"=>{"context_id"=>"1", "project_id"=>"2", "id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo, bar" xhr :post, :update, :id => 1, :_source_view => 'todo', "todo"=>{"context_id"=>"1", "project_id"=>"2", "id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo, bar"
t = Todo.find(1) t = Todo.find(1)
assert_equal "Call Warren Buffet to find out how much he makes per day", t.description assert_equal "Call Warren Buffet to find out how much he makes per day", t.description
assert_equal "foo, bar", t.tag_list assert_equal "bar, foo", t.tag_list
expected = Date.new(2006,11,30) expected = Date.new(2006,11,30)
actual = t.due.to_date actual = t.due.to_date
assert_equal expected, actual, "Expected #{expected.to_s(:db)}, was #{actual.to_s(:db)}" assert_equal expected, actual, "Expected #{expected.to_s(:db)}, was #{actual.to_s(:db)}"
end end
def test_update_todos_with_blank_project_name def test_update_todos_with_blank_project_name
t = Todo.find(1) t = Todo.find(1)
login_as(:admin_user) login_as(:admin_user)
xhr :post, :update, :id => 1, :_source_view => 'todo', :project_name => '', "todo"=>{"id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo, bar" xhr :post, :update, :id => 1, :_source_view => 'todo', :project_name => '', "todo"=>{"id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"foo, bar"
t.reload t.reload
assert t.project.nil? assert t.project.nil?
end end
def test_update_todo_tags_to_none def test_update_todo_tags_to_none
t = Todo.find(1) t = Todo.find(1)
login_as(:admin_user) login_as(:admin_user)
xhr :post, :update, :id => 1, :_source_view => 'todo', "todo"=>{"context_id"=>"1", "project_id"=>"2", "id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>"" xhr :post, :update, :id => 1, :_source_view => 'todo', "todo"=>{"context_id"=>"1", "project_id"=>"2", "id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>""
t = Todo.find(1) t = Todo.find(1)
assert_equal true, t.tag_list.empty? assert_equal true, t.tag_list.empty?
end end
def test_update_todo_tags_with_whitespace_and_dots def test_update_todo_tags_with_whitespace_and_dots
t = Todo.find(1) t = Todo.find(1)
login_as(:admin_user) login_as(:admin_user)
taglist = " one , two,three ,four, 8.1.2, version1.5" taglist = " one , two,three ,four, 8.1.2, version1.5"
xhr :post, :update, :id => 1, :_source_view => 'todo', "todo"=>{"context_id"=>"1", "project_id"=>"2", "id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>taglist xhr :post, :update, :id => 1, :_source_view => 'todo', "todo"=>{"context_id"=>"1", "project_id"=>"2", "id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006"}, "tag_list"=>taglist
t = Todo.find(1) t = Todo.find(1)
assert_equal "one, two, three, four, 8.1.2, version1.5", t.tag_list assert_equal "8.1.2, four, one, three, two, version1.5", t.tag_list
end end
def test_find_tagged_with def test_find_tagged_with
login_as(:admin_user) login_as(:admin_user)
@user = User.find(@request.session['user_id']) @user = User.find(@request.session['user_id'])
tag = Tag.find_by_name('foo').todos tag = Tag.find_by_name('foo').todos
@tagged = tag.find(:all, :conditions => ['taggings.user_id = ?', @user.id]).size @tagged = tag.find(:all, :conditions => ['taggings.user_id = ?', @user.id]).size
get :tag, :name => 'foo' get :tag, :name => 'foo'
assert_response :success assert_response :success
assert_equal 3, @tagged assert_equal 3, @tagged
end end
def test_rss_feed def test_rss_feed
login_as(:admin_user) login_as(:admin_user)
get :index, { :format => "rss" } get :index, { :format => "rss" }
assert_equal 'application/rss+xml', @response.content_type assert_equal 'application/rss+xml', @response.content_type
# puts @response.body # puts @response.body
assert_xml_select 'rss[version="2.0"]' do assert_xml_select 'rss[version="2.0"]' do
assert_select 'channel' do assert_select 'channel' do
assert_select '>title', 'Actions' assert_select '>title', 'Actions'
assert_select '>description', "Actions for #{users(:admin_user).display_name}" assert_select '>description', "Actions for #{users(:admin_user).display_name}"
assert_select 'language', 'en-us' assert_select 'language', 'en-us'
assert_select 'ttl', '40' assert_select 'ttl', '40'
assert_select 'item', 11 do assert_select 'item', 11 do
assert_select 'title', /.+/ assert_select 'title', /.+/
assert_select 'description', /.*/ assert_select 'description', /.*/
assert_select 'link', %r{http://test.host/contexts/.+} assert_select 'link', %r{http://test.host/contexts/.+}
assert_select 'guid', %r{http://test.host/todos/.+} assert_select 'guid', %r{http://test.host/todos/.+}
assert_select 'pubDate', todos(:book).updated_at.to_s(:rfc822) assert_select 'pubDate', todos(:book).updated_at.to_s(:rfc822)
end end
end end
end end
end end
def test_rss_feed_with_limit def test_rss_feed_with_limit
login_as(:admin_user) login_as(:admin_user)
get :index, { :format => "rss", :limit => '5' } get :index, { :format => "rss", :limit => '5' }
assert_xml_select 'rss[version="2.0"]' do assert_xml_select 'rss[version="2.0"]' do
assert_select 'channel' do assert_select 'channel' do
assert_select '>title', 'Actions' assert_select '>title', 'Actions'
assert_select '>description', "Actions for #{users(:admin_user).display_name}" assert_select '>description', "Actions for #{users(:admin_user).display_name}"
assert_select 'item', 5 do assert_select 'item', 5 do
assert_select 'title', /.+/ assert_select 'title', /.+/
assert_select 'description', /.*/ assert_select 'description', /.*/
end end
end end
end end
end end
def test_rss_feed_not_accessible_to_anonymous_user_without_token def test_rss_feed_not_accessible_to_anonymous_user_without_token
login_as nil login_as nil
get :index, { :format => "rss" } get :index, { :format => "rss" }
assert_response 401 assert_response 401
end end
def test_rss_feed_not_accessible_to_anonymous_user_with_invalid_token def test_rss_feed_not_accessible_to_anonymous_user_with_invalid_token
login_as nil login_as nil
get :index, { :format => "rss", :token => 'foo' } get :index, { :format => "rss", :token => 'foo' }
assert_response 401 assert_response 401
end end
def test_rss_feed_accessible_to_anonymous_user_with_valid_token def test_rss_feed_accessible_to_anonymous_user_with_valid_token
login_as nil login_as nil
get :index, { :format => "rss", :token => users(:admin_user).token } get :index, { :format => "rss", :token => users(:admin_user).token }
assert_response :ok assert_response :ok
end end
def test_atom_feed_content def test_atom_feed_content
login_as :admin_user login_as :admin_user
get :index, { :format => "atom" } get :index, { :format => "atom" }
assert_equal 'application/atom+xml', @response.content_type assert_equal 'application/atom+xml', @response.content_type
# #puts @response.body # #puts @response.body
assert_xml_select 'feed[xmlns="http://www.w3.org/2005/Atom"]' do assert_xml_select 'feed[xmlns="http://www.w3.org/2005/Atom"]' do
assert_xml_select '>title', 'Actions' assert_xml_select '>title', 'Actions'
assert_xml_select '>subtitle', "Actions for #{users(:admin_user).display_name}" assert_xml_select '>subtitle', "Actions for #{users(:admin_user).display_name}"
assert_xml_select 'entry', 11 do assert_xml_select 'entry', 11 do
assert_xml_select 'title', /.+/ assert_xml_select 'title', /.+/
assert_xml_select 'content[type="html"]', /.*/ assert_xml_select 'content[type="html"]', /.*/
assert_xml_select 'published', /(#{Regexp.escape(todos(:book).updated_at.xmlschema)}|#{Regexp.escape(projects(:moremoney).updated_at.xmlschema)})/ assert_xml_select 'published', /(#{Regexp.escape(todos(:book).updated_at.xmlschema)}|#{Regexp.escape(projects(:moremoney).updated_at.xmlschema)})/
end end
end end
end end
def test_atom_feed_not_accessible_to_anonymous_user_without_token def test_atom_feed_not_accessible_to_anonymous_user_without_token
login_as nil login_as nil
get :index, { :format => "atom" } get :index, { :format => "atom" }
assert_response 401 assert_response 401
end end
def test_atom_feed_not_accessible_to_anonymous_user_with_invalid_token def test_atom_feed_not_accessible_to_anonymous_user_with_invalid_token
login_as nil login_as nil
get :index, { :format => "atom", :token => 'foo' } get :index, { :format => "atom", :token => 'foo' }
assert_response 401 assert_response 401
end end
def test_atom_feed_accessible_to_anonymous_user_with_valid_token def test_atom_feed_accessible_to_anonymous_user_with_valid_token
login_as nil login_as nil
get :index, { :format => "atom", :token => users(:admin_user).token } get :index, { :format => "atom", :token => users(:admin_user).token }
assert_response :ok assert_response :ok
end end
def test_text_feed_content def test_text_feed_content
login_as(:admin_user) login_as(:admin_user)
get :index, { :format => "txt" } get :index, { :format => "txt" }
assert_equal 'text/plain', @response.content_type assert_equal 'text/plain', @response.content_type
assert !(/&nbsp;/.match(@response.body)) assert !(/&nbsp;/.match(@response.body))
# #puts @response.body # #puts @response.body
end end
def test_text_feed_not_accessible_to_anonymous_user_without_token def test_text_feed_not_accessible_to_anonymous_user_without_token
login_as nil login_as nil
get :index, { :format => "txt" } get :index, { :format => "txt" }
assert_response 401 assert_response 401
end end
def test_text_feed_not_accessible_to_anonymous_user_with_invalid_token def test_text_feed_not_accessible_to_anonymous_user_with_invalid_token
login_as nil login_as nil
get :index, { :format => "txt", :token => 'foo' } get :index, { :format => "txt", :token => 'foo' }
assert_response 401 assert_response 401
end end
def test_text_feed_accessible_to_anonymous_user_with_valid_token def test_text_feed_accessible_to_anonymous_user_with_valid_token
login_as nil login_as nil
get :index, { :format => "txt", :token => users(:admin_user).token } get :index, { :format => "txt", :token => users(:admin_user).token }
assert_response :ok assert_response :ok
end end
def test_ical_feed_content def test_ical_feed_content
login_as :admin_user login_as :admin_user
get :index, { :format => "ics" } get :index, { :format => "ics" }
assert_equal 'text/calendar', @response.content_type assert_equal 'text/calendar', @response.content_type
assert !(/&nbsp;/.match(@response.body)) assert !(/&nbsp;/.match(@response.body))
# #puts @response.body # #puts @response.body
end end
def test_mobile_index_uses_text_html_content_type def test_mobile_index_uses_text_html_content_type
login_as(:admin_user) login_as(:admin_user)
get :index, { :format => "m" } get :index, { :format => "m" }
assert_equal 'text/html', @response.content_type assert_equal 'text/html', @response.content_type
end end
def test_mobile_index_assigns_down_count def test_mobile_index_assigns_down_count
login_as(:admin_user) login_as(:admin_user)
get :index, { :format => "m" } get :index, { :format => "m" }
assert_equal 11, assigns['down_count'] assert_equal 11, assigns['down_count']
end end
def test_mobile_create_action_creates_a_new_todo def test_mobile_create_action_creates_a_new_todo
login_as(:admin_user) login_as(:admin_user)
post :create, {"format"=>"m", "todo"=>{"context_id"=>"2", post :create, {"format"=>"m", "todo"=>{"context_id"=>"2",
"due(1i)"=>"2007", "due(2i)"=>"1", "due(3i)"=>"2", "due(1i)"=>"2007", "due(2i)"=>"1", "due(3i)"=>"2",
"show_from(1i)"=>"", "show_from(2i)"=>"", "show_from(3i)"=>"", "show_from(1i)"=>"", "show_from(2i)"=>"", "show_from(3i)"=>"",
"project_id"=>"1", "project_id"=>"1",
"notes"=>"test notes", "description"=>"test_mobile_create_action", "state"=>"0"}} "notes"=>"test notes", "description"=>"test_mobile_create_action", "state"=>"0"}}
t = Todo.find_by_description("test_mobile_create_action") t = Todo.find_by_description("test_mobile_create_action")
assert_not_nil t assert_not_nil t
assert_equal 2, t.context_id assert_equal 2, t.context_id
assert_equal 1, t.project_id assert_equal 1, t.project_id
assert t.active? assert t.active?
assert_equal 'test notes', t.notes assert_equal 'test notes', t.notes
assert_nil t.show_from assert_nil t.show_from
assert_equal Date.new(2007,1,2), t.due.to_date assert_equal Date.new(2007,1,2), t.due.to_date
end end
def test_mobile_create_action_redirects_to_mobile_home_page_when_successful def test_mobile_create_action_redirects_to_mobile_home_page_when_successful
login_as(:admin_user) login_as(:admin_user)
post :create, {"format"=>"m", "todo"=>{"context_id"=>"2", post :create, {"format"=>"m", "todo"=>{"context_id"=>"2",
"due(1i)"=>"2007", "due(2i)"=>"1", "due(3i)"=>"2", "due(1i)"=>"2007", "due(2i)"=>"1", "due(3i)"=>"2",
"show_from(1i)"=>"", "show_from(2i)"=>"", "show_from(3i)"=>"", "show_from(1i)"=>"", "show_from(2i)"=>"", "show_from(3i)"=>"",
"project_id"=>"1", "project_id"=>"1",
"notes"=>"test notes", "description"=>"test_mobile_create_action", "state"=>"0"}} "notes"=>"test notes", "description"=>"test_mobile_create_action", "state"=>"0"}}
assert_redirected_to '/m' assert_redirected_to '/m'
end end
def test_mobile_create_action_renders_new_template_when_save_fails def test_mobile_create_action_renders_new_template_when_save_fails
login_as(:admin_user) login_as(:admin_user)
post :create, {"format"=>"m", "todo"=>{"context_id"=>"2", post :create, {"format"=>"m", "todo"=>{"context_id"=>"2",
"due(1i)"=>"2007", "due(2i)"=>"1", "due(3i)"=>"2", "due(1i)"=>"2007", "due(2i)"=>"1", "due(3i)"=>"2",
"show_from(1i)"=>"", "show_from(2i)"=>"", "show_from(3i)"=>"", "show_from(1i)"=>"", "show_from(2i)"=>"", "show_from(3i)"=>"",
"project_id"=>"1", "project_id"=>"1",
"notes"=>"test notes", "state"=>"0"}, "tag_list"=>"test, test2"} "notes"=>"test notes", "state"=>"0"}, "tag_list"=>"test, test2"}
assert_template 'todos/new' assert_template 'todos/new'
end end
def test_index_html_assigns_default_project_name_map def test_index_html_assigns_default_project_name_map
login_as(:admin_user) login_as(:admin_user)
get :index, {"format"=>"html"} get :index, {"format"=>"html"}
assert_equal '"{\\"Build a working time machine\\": \\"lab\\"}"', assigns(:default_project_context_name_map) assert_equal '"{\\"Build a working time machine\\": \\"lab\\"}"', assigns(:default_project_context_name_map)
end end
def test_toggle_check_on_recurring_todo def test_toggle_check_on_recurring_todo
login_as(:admin_user) login_as(:admin_user)
# link todo_1 and recurring_todo_1 # link todo_1 and recurring_todo_1
recurring_todo_1 = RecurringTodo.find(1) recurring_todo_1 = RecurringTodo.find(1)
todo_1 = Todo.find_by_recurring_todo_id(1) todo_1 = Todo.find_by_recurring_todo_id(1)
# mark todo_1 as complete by toggle_check # mark todo_1 as complete by toggle_check
xhr :post, :toggle_check, :id => todo_1.id, :_source_view => 'todo' xhr :post, :toggle_check, :id => todo_1.id, :_source_view => 'todo'
todo_1.reload todo_1.reload
assert todo_1.completed? assert todo_1.completed?
# check that there is only one active todo belonging to recurring_todo # check that there is only one active todo belonging to recurring_todo
count = Todo.count(:all, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'active'}) count = Todo.count(:all, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'active'})
assert_equal 1, count assert_equal 1, count
# check there is a new todo linked to the recurring pattern # check there is a new todo linked to the recurring pattern
next_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'active'}) next_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'active'})
assert_equal "Call Bill Gates every day", next_todo.description assert_equal "Call Bill Gates every day", next_todo.description
# check that the new todo is not the same as todo_1 # check that the new todo is not the same as todo_1
assert_not_equal todo_1.id, next_todo.id assert_not_equal todo_1.id, next_todo.id
# change recurrence pattern to monthly and set show_from 2 days before due # change recurrence pattern to monthly and set show_from 2 days before due
# date this forces the next todo to be put in the tickler # date this forces the next todo to be put in the tickler
recurring_todo_1.show_from_delta = 2 recurring_todo_1.show_from_delta = 2
recurring_todo_1.recurring_period = 'monthly' recurring_todo_1.recurring_period = 'monthly'
recurring_todo_1.recurrence_selector = 0 recurring_todo_1.recurrence_selector = 0
recurring_todo_1.every_other1 = 1 recurring_todo_1.every_other1 = 1
recurring_todo_1.every_other2 = 2 recurring_todo_1.every_other2 = 2
recurring_todo_1.every_other3 = 5 recurring_todo_1.every_other3 = 5
recurring_todo_1.save recurring_todo_1.save
# mark next_todo as complete by toggle_check # mark next_todo as complete by toggle_check
xhr :post, :toggle_check, :id => next_todo.id, :_source_view => 'todo' xhr :post, :toggle_check, :id => next_todo.id, :_source_view => 'todo'
next_todo.reload next_todo.reload
assert next_todo.completed? assert next_todo.completed?
# check that there are three todos belonging to recurring_todo: two # check that there are three todos belonging to recurring_todo: two
# completed and one deferred # completed and one deferred
count = Todo.count(:all, :conditions => {:recurring_todo_id => recurring_todo_1.id}) count = Todo.count(:all, :conditions => {:recurring_todo_id => recurring_todo_1.id})
assert_equal 3, count assert_equal 3, count
# check there is a new todo linked to the recurring pattern in the tickler # check there is a new todo linked to the recurring pattern in the tickler
next_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'deferred'}) next_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'deferred'})
assert !next_todo.nil? assert !next_todo.nil?
assert_equal "Call Bill Gates every day", next_todo.description assert_equal "Call Bill Gates every day", next_todo.description
# check that the todo is in the tickler # check that the todo is in the tickler
assert !next_todo.show_from.nil? assert !next_todo.show_from.nil?
end end
def test_toggle_check_on_rec_todo_show_from_today def test_toggle_check_on_rec_todo_show_from_today
login_as(:admin_user) login_as(:admin_user)
# link todo_1 and recurring_todo_1 # link todo_1 and recurring_todo_1
recurring_todo_1 = RecurringTodo.find(1) recurring_todo_1 = RecurringTodo.find(1)
todo_1 = Todo.find_by_recurring_todo_id(1) todo_1 = Todo.find_by_recurring_todo_id(1)
today = Time.now.utc.at_midnight today = Time.now.utc.at_midnight
# change recurrence pattern to monthly and set show_from to today # change recurrence pattern to monthly and set show_from to today
recurring_todo_1.target = 'show_from_date' recurring_todo_1.target = 'show_from_date'
recurring_todo_1.recurring_period = 'monthly' recurring_todo_1.recurring_period = 'monthly'
recurring_todo_1.recurrence_selector = 0 recurring_todo_1.recurrence_selector = 0
recurring_todo_1.every_other1 = today.day recurring_todo_1.every_other1 = today.day
recurring_todo_1.every_other2 = 1 recurring_todo_1.every_other2 = 1
recurring_todo_1.save recurring_todo_1.save
# mark todo_1 as complete by toggle_check, this gets rid of todo_1 that was # mark todo_1 as complete by toggle_check, this gets rid of todo_1 that was
# not correctly created from the adjusted recurring pattern we defined # not correctly created from the adjusted recurring pattern we defined
# above. # above.
xhr :post, :toggle_check, :id => todo_1.id, :_source_view => 'todo' xhr :post, :toggle_check, :id => todo_1.id, :_source_view => 'todo'
todo_1.reload todo_1.reload
assert todo_1.completed? assert todo_1.completed?
# locate the new todo. This todo is created from the adjusted recurring # locate the new todo. This todo is created from the adjusted recurring
# pattern defined in this test # pattern defined in this test
new_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'active'}) new_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'active'})
assert !new_todo.nil? assert !new_todo.nil?
# mark new_todo as complete by toggle_check # mark new_todo as complete by toggle_check
xhr :post, :toggle_check, :id => new_todo.id, :_source_view => 'todo' xhr :post, :toggle_check, :id => new_todo.id, :_source_view => 'todo'
new_todo.reload new_todo.reload
assert todo_1.completed? assert todo_1.completed?
# locate the new todo in tickler # locate the new todo in tickler
new_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'deferred'}) new_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'deferred'})
assert !new_todo.nil? assert !new_todo.nil?
assert_equal "Call Bill Gates every day", new_todo.description assert_equal "Call Bill Gates every day", new_todo.description
# check that the new todo is not the same as todo_1 # check that the new todo is not the same as todo_1
assert_not_equal todo_1.id, new_todo.id assert_not_equal todo_1.id, new_todo.id
# check that the new_todo is in the tickler to show next month # check that the new_todo is in the tickler to show next month
assert !new_todo.show_from.nil? assert !new_todo.show_from.nil?
assert_equal Time.utc(today.year, today.month, today.day)+1.month, new_todo.show_from assert_equal Time.utc(today.year, today.month, today.day)+1.month, new_todo.show_from
end end
def test_check_for_next_todo def test_check_for_next_todo
login_as :admin_user login_as :admin_user
recurring_todo_1 = RecurringTodo.find(5) recurring_todo_1 = RecurringTodo.find(5)
@todo = Todo.find_by_recurring_todo_id(1) @todo = Todo.find_by_recurring_todo_id(1)
assert @todo.from_recurring_todo? assert @todo.from_recurring_todo?
# rewire @todo to yearly recurring todo # rewire @todo to yearly recurring todo
@todo.recurring_todo_id = 5 @todo.recurring_todo_id = 5
# make todo due tomorrow and change recurring date also to tomorrow # make todo due tomorrow and change recurring date also to tomorrow
@todo.due = Time.zone.now + 1.day @todo.due = Time.zone.now + 1.day
@todo.save @todo.save
recurring_todo_1.every_other1 = @todo.due.day recurring_todo_1.every_other1 = @todo.due.day
recurring_todo_1.every_other2 = @todo.due.month recurring_todo_1.every_other2 = @todo.due.month
recurring_todo_1.save recurring_todo_1.save
# mark todo complete # mark todo complete
xhr :post, :toggle_check, :id => @todo.id, :_source_view => 'todo' xhr :post, :toggle_check, :id => @todo.id, :_source_view => 'todo'
@todo.reload @todo.reload
assert @todo.completed? assert @todo.completed?
# check that there is no active todo # check that there is no active todo
next_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'active'}) next_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'active'})
assert next_todo.nil? assert next_todo.nil?
# check for new deferred todo # check for new deferred todo
next_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'deferred'}) next_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'deferred'})
assert !next_todo.nil? assert !next_todo.nil?
# check that the due date of the new todo is later than tomorrow # check that the due date of the new todo is later than tomorrow
assert next_todo.due > @todo.due assert next_todo.due > @todo.due
end end
end end

View file

@ -1,18 +1,18 @@
setup :fixtures => :all setup :fixtures => :all
login :as => 'admin' login :as => 'admin'
open '/m' open '/m'
wait_for_text 'css=h1 span.count', '11' wait_for_text 'css=h1 span.count', '11'
click_and_wait "link=0-Add new action" click_and_wait "link=0-New action"
type "todo_notes", "test notes" type "todo_notes", "test notes"
type "todo_description", "test name" type "todo_description", "test name"
select "todo_context_id", "label=call" select "todo_context_id", "label=call"
select "todo_project_id", "label=Make more money than Billy Gates" select "todo_project_id", "label=Make more money than Billy Gates"
select "todo_due_3i", "label=1" select "todo_due_3i", "label=1"
select "todo_due_2i", "label=January" select "todo_due_2i", "label=January"
select "todo_due_1i", "label=2009" select "todo_due_1i", "label=2009"
click_and_wait "//input[@value='Create']" click_and_wait "//input[@value='Create']"
wait_for_text 'css=h1 span.count', '12' wait_for_text 'css=h1 span.count', '12'

View file

@ -1,283 +1,283 @@
require File.dirname(__FILE__) + '/../test_helper' require File.dirname(__FILE__) + '/../test_helper'
class RecurringTodoTest < Test::Rails::TestCase class RecurringTodoTest < Test::Rails::TestCase
fixtures :todos, :users, :contexts, :preferences, :tags, :taggings, :recurring_todos fixtures :todos, :users, :contexts, :preferences, :tags, :taggings, :recurring_todos
def setup def setup
@every_day = RecurringTodo.find(1).reload @every_day = RecurringTodo.find(1).reload
@every_workday = RecurringTodo.find(2).reload @every_workday = RecurringTodo.find(2).reload
@weekly_every_day = RecurringTodo.find(3).reload @weekly_every_day = RecurringTodo.find(3).reload
@monthly_every_last_friday = RecurringTodo.find(4).reload @monthly_every_last_friday = RecurringTodo.find(4).reload
@yearly = RecurringTodo.find(5).reload @yearly = RecurringTodo.find(5).reload
@today = Time.now.utc @today = Time.now.utc
@tomorrow = @today + 1.day @tomorrow = @today + 1.day
@in_three_days = Time.now.utc + 3.days @in_three_days = Time.now.utc + 3.days
@in_four_days = @in_three_days + 1.day # need a day after start_from @in_four_days = @in_three_days + 1.day # need a day after start_from
@friday = Time.zone.local(2008,6,6) @friday = Time.zone.local(2008,6,6)
@saturday = Time.zone.local(2008,6,7) @saturday = Time.zone.local(2008,6,7)
@sunday = Time.zone.local(2008,6,8) # june 8, 2008 was a sunday @sunday = Time.zone.local(2008,6,8) # june 8, 2008 was a sunday
@monday = Time.zone.local(2008,6,9) @monday = Time.zone.local(2008,6,9)
@tuesday = Time.zone.local(2008,6,10) @tuesday = Time.zone.local(2008,6,10)
@wednesday = Time.zone.local(2008,6,11) @wednesday = Time.zone.local(2008,6,11)
@thursday = Time.zone.local(2008,6,12) @thursday = Time.zone.local(2008,6,12)
end end
def test_pattern_text def test_pattern_text
assert_equal "every day", @every_day.recurrence_pattern assert_equal "every day", @every_day.recurrence_pattern
assert_equal "on work days", @every_workday.recurrence_pattern assert_equal "on work days", @every_workday.recurrence_pattern
assert_equal "every last Friday of every 2 months", @monthly_every_last_friday.recurrence_pattern assert_equal "every last Friday of every 2 months", @monthly_every_last_friday.recurrence_pattern
assert_equal "every year on June 8", @yearly.recurrence_pattern assert_equal "every year on June 8", @yearly.recurrence_pattern
end end
def test_daily_every_day def test_daily_every_day
# every_day should return todays date if there was no previous date # every_day should return todays date if there was no previous date
due_date = @every_day.get_due_date(nil) due_date = @every_day.get_due_date(nil)
# use strftime in compare, because milisec / secs could be different # use strftime in compare, because milisec / secs could be different
assert_equal @today.strftime("%d-%m-%y"), due_date.strftime("%d-%m-%y") assert_equal @today.strftime("%d-%m-%y"), due_date.strftime("%d-%m-%y")
# when the last todo was completed today, the next todo is due tomorrow # when the last todo was completed today, the next todo is due tomorrow
due_date =@every_day.get_due_date(@today) due_date =@every_day.get_due_date(@today)
assert_equal @tomorrow, due_date assert_equal @tomorrow, due_date
# do something every 14 days # do something every 14 days
@every_day.every_other1=14 @every_day.every_other1=14
due_date = @every_day.get_due_date(@today) due_date = @every_day.get_due_date(@today)
assert_equal @today+14.days, due_date assert_equal @today+14.days, due_date
end end
def test_daily_work_days def test_daily_work_days
assert_equal @monday, @every_workday.get_due_date(@friday) assert_equal @monday, @every_workday.get_due_date(@friday)
assert_equal @monday, @every_workday.get_due_date(@saturday) assert_equal @monday, @every_workday.get_due_date(@saturday)
assert_equal @monday, @every_workday.get_due_date(@sunday) assert_equal @monday, @every_workday.get_due_date(@sunday)
assert_equal @tuesday, @every_workday.get_due_date(@monday) assert_equal @tuesday, @every_workday.get_due_date(@monday)
end end
def test_show_from_date def test_show_from_date
# assume that target due_date works fine, i.e. don't do the same tests over # assume that target due_date works fine, i.e. don't do the same tests over
@every_day.target='show_from_date' @every_day.target='show_from_date'
# when recurrence is targeted on show_from, due date shoult remain nil # when recurrence is targeted on show_from, due date shoult remain nil
assert_equal nil, @every_day.get_due_date(nil) assert_equal nil, @every_day.get_due_date(nil)
assert_equal nil, @every_day.get_due_date(@today-3.days) assert_equal nil, @every_day.get_due_date(@today-3.days)
# check show from get the next day # check show from get the next day
assert_equal @today, @every_day.get_show_from_date(@today-1.days) assert_equal @today, @every_day.get_show_from_date(@today-1.days)
assert_equal @today+1.day, @every_day.get_show_from_date(@today) assert_equal @today+1.day, @every_day.get_show_from_date(@today)
@every_day.target='due_date' @every_day.target='due_date'
# when target on due_date, show_from is relative to due date unless delta=0 # when target on due_date, show_from is relative to due date unless delta=0
assert_equal nil, @every_day.get_show_from_date(@today-1.days) assert_equal nil, @every_day.get_show_from_date(@today-1.days)
@every_day.show_from_delta=10 @every_day.show_from_delta=10
assert_equal @today, @every_day.get_show_from_date(@today+9.days) #today+1+9-10 assert_equal @today, @every_day.get_show_from_date(@today+9.days) #today+1+9-10
# TODO: show_from has no use case for daily pattern. Need to test on # TODO: show_from has no use case for daily pattern. Need to test on
# weekly/monthly/yearly # weekly/monthly/yearly
end end
def test_end_date_on_recurring_todo def test_end_date_on_recurring_todo
assert_equal true, @every_day.has_next_todo(@in_three_days) assert_equal true, @every_day.has_next_todo(@in_three_days)
assert_equal true, @every_day.has_next_todo(@in_four_days) assert_equal true, @every_day.has_next_todo(@in_four_days)
@every_day.end_date = @in_four_days @every_day.end_date = @in_four_days
assert_equal false, @every_day.has_next_todo(@in_four_days) assert_equal false, @every_day.has_next_todo(@in_four_days)
end end
def test_weekly_every_day_setters def test_weekly_every_day_setters
@weekly_every_day.every_day = ' ' @weekly_every_day.every_day = ' '
@weekly_every_day.weekly_return_sunday=('s') @weekly_every_day.weekly_return_sunday=('s')
assert_equal 's ', @weekly_every_day.every_day assert_equal 's ', @weekly_every_day.every_day
@weekly_every_day.weekly_return_monday=('m') @weekly_every_day.weekly_return_monday=('m')
assert_equal 'sm ', @weekly_every_day.every_day assert_equal 'sm ', @weekly_every_day.every_day
@weekly_every_day.weekly_return_tuesday=('t') @weekly_every_day.weekly_return_tuesday=('t')
assert_equal 'smt ', @weekly_every_day.every_day assert_equal 'smt ', @weekly_every_day.every_day
@weekly_every_day.weekly_return_wednesday=('w') @weekly_every_day.weekly_return_wednesday=('w')
assert_equal 'smtw ', @weekly_every_day.every_day assert_equal 'smtw ', @weekly_every_day.every_day
@weekly_every_day.weekly_return_thursday=('t') @weekly_every_day.weekly_return_thursday=('t')
assert_equal 'smtwt ', @weekly_every_day.every_day assert_equal 'smtwt ', @weekly_every_day.every_day
@weekly_every_day.weekly_return_friday=('f') @weekly_every_day.weekly_return_friday=('f')
assert_equal 'smtwtf ', @weekly_every_day.every_day assert_equal 'smtwtf ', @weekly_every_day.every_day
@weekly_every_day.weekly_return_saturday=('s') @weekly_every_day.weekly_return_saturday=('s')
assert_equal 'smtwtfs', @weekly_every_day.every_day assert_equal 'smtwtfs', @weekly_every_day.every_day
# test remove # test remove
@weekly_every_day.weekly_return_wednesday=(' ') @weekly_every_day.weekly_return_wednesday=(' ')
assert_equal 'smt tfs', @weekly_every_day.every_day assert_equal 'smt tfs', @weekly_every_day.every_day
end end
def test_weekly_pattern def test_weekly_pattern
assert_equal true, @weekly_every_day.has_next_todo(nil) assert_equal true, @weekly_every_day.has_next_todo(nil)
due_date = @weekly_every_day.get_due_date(@sunday) due_date = @weekly_every_day.get_due_date(@sunday)
assert_equal @monday, due_date assert_equal @monday, due_date
# saturday is last day in week, so the next date should be sunday + n_weeks # saturday is last day in week, so the next date should be sunday + n_weeks
due_date = @weekly_every_day.get_due_date(@saturday) due_date = @weekly_every_day.get_due_date(@saturday)
assert_equal @sunday + 2.weeks, due_date assert_equal @sunday + 2.weeks, due_date
# remove tuesday and wednesday # remove tuesday and wednesday
@weekly_every_day.weekly_return_tuesday=(' ') @weekly_every_day.weekly_return_tuesday=(' ')
@weekly_every_day.weekly_return_wednesday=(' ') @weekly_every_day.weekly_return_wednesday=(' ')
assert_equal 'sm tfs', @weekly_every_day.every_day assert_equal 'sm tfs', @weekly_every_day.every_day
due_date = @weekly_every_day.get_due_date(@monday) due_date = @weekly_every_day.get_due_date(@monday)
assert_equal @thursday, due_date assert_equal @thursday, due_date
@weekly_every_day.every_other1 = 1 @weekly_every_day.every_other1 = 1
@weekly_every_day.every_day = ' tw ' @weekly_every_day.every_day = ' tw '
due_date = @weekly_every_day.get_due_date(@tuesday) due_date = @weekly_every_day.get_due_date(@tuesday)
assert_equal @wednesday, due_date assert_equal @wednesday, due_date
due_date = @weekly_every_day.get_due_date(@wednesday) due_date = @weekly_every_day.get_due_date(@wednesday)
assert_equal @tuesday+1.week, due_date assert_equal @tuesday+1.week, due_date
end end
def test_monthly_pattern def test_monthly_pattern
due_date = @monthly_every_last_friday.get_due_date(@sunday) due_date = @monthly_every_last_friday.get_due_date(@sunday)
assert_equal Time.zone.local(2008,6,27), due_date assert_equal Time.zone.local(2008,6,27), due_date
friday_is_last_day_of_month = Time.zone.local(2008,10,31) friday_is_last_day_of_month = Time.zone.local(2008,10,31)
due_date = @monthly_every_last_friday.get_due_date(friday_is_last_day_of_month-1.day ) due_date = @monthly_every_last_friday.get_due_date(friday_is_last_day_of_month-1.day )
assert_equal friday_is_last_day_of_month , due_date assert_equal friday_is_last_day_of_month , due_date
@monthly_every_third_friday = @monthly_every_last_friday @monthly_every_third_friday = @monthly_every_last_friday
@monthly_every_third_friday.every_other3=3 #third @monthly_every_third_friday.every_other3=3 #third
due_date = @monthly_every_last_friday.get_due_date(@sunday) # june 8th 2008 due_date = @monthly_every_last_friday.get_due_date(@sunday) # june 8th 2008
assert_equal Time.zone.local(2008, 6, 20), due_date assert_equal Time.zone.local(2008, 6, 20), due_date
# set date past third friday of this month # set date past third friday of this month
due_date = @monthly_every_last_friday.get_due_date(Time.zone.local(2008,6,21)) # june 21th 2008 due_date = @monthly_every_last_friday.get_due_date(Time.zone.local(2008,6,21)) # june 21th 2008
assert_equal Time.zone.local(2008, 8, 15), due_date # every 2 months, so aug assert_equal Time.zone.local(2008, 8, 15), due_date # every 2 months, so aug
@monthly = @monthly_every_last_friday @monthly = @monthly_every_last_friday
@monthly.recurrence_selector=0 @monthly.recurrence_selector=0
@monthly.every_other1 = 8 # every 8th day of the month @monthly.every_other1 = 8 # every 8th day of the month
@monthly.every_other2 = 2 # every 2 months @monthly.every_other2 = 2 # every 2 months
due_date = @monthly.get_due_date(@saturday) # june 7th due_date = @monthly.get_due_date(@saturday) # june 7th
assert_equal @sunday, due_date # june 8th assert_equal @sunday, due_date # june 8th
due_date = @monthly.get_due_date(@sunday) # june 8th due_date = @monthly.get_due_date(@sunday) # june 8th
assert_equal Time.zone.local(2008,8,8), due_date # aug 8th assert_equal Time.zone.local(2008,8,8), due_date # aug 8th
end end
def test_yearly_pattern def test_yearly_pattern
# beginning of same year # beginning of same year
due_date = @yearly.get_due_date(Time.zone.local(2008,2,10)) # feb 10th due_date = @yearly.get_due_date(Time.zone.local(2008,2,10)) # feb 10th
assert_equal @sunday, due_date # june 8th assert_equal @sunday, due_date # june 8th
# same month, previous date # same month, previous date
due_date = @yearly.get_due_date(@saturday) # june 7th due_date = @yearly.get_due_date(@saturday) # june 7th
show_from_date = @yearly.get_show_from_date(@saturday) # june 7th show_from_date = @yearly.get_show_from_date(@saturday) # june 7th
assert_equal @sunday, due_date # june 8th assert_equal @sunday, due_date # june 8th
assert_equal @sunday-5.days, show_from_date assert_equal @sunday-5.days, show_from_date
# same month, day after # same month, day after
due_date = @yearly.get_due_date(@monday) # june 9th due_date = @yearly.get_due_date(@monday) # june 9th
assert_equal Time.zone.local(2009,6,8), due_date # june 8th next year assert_equal Time.zone.local(2009,6,8), due_date # june 8th next year
# very overdue # very overdue
due_date = @yearly.get_due_date(@monday+5.months-2.days) # november 7 due_date = @yearly.get_due_date(@monday+5.months-2.days) # november 7
assert_equal Time.zone.local(2009,6,8), due_date # june 8th next year assert_equal Time.zone.local(2009,6,8), due_date # june 8th next year
@yearly.recurrence_selector = 1 @yearly.recurrence_selector = 1
@yearly.every_other3 = 2 # second @yearly.every_other3 = 2 # second
@yearly.every_count = 3 # wednesday @yearly.every_count = 3 # wednesday
# beginning of same year # beginning of same year
due_date = @yearly.get_due_date(Time.zone.local(2008,2,10)) # feb 10th due_date = @yearly.get_due_date(Time.zone.local(2008,2,10)) # feb 10th
assert_equal Time.zone.local(2008,6,11), due_date # june 11th assert_equal Time.zone.local(2008,6,11), due_date # june 11th
# same month, before second wednesday # same month, before second wednesday
due_date = @yearly.get_due_date(@saturday) # june 7th due_date = @yearly.get_due_date(@saturday) # june 7th
assert_equal Time.zone.local(2008,6,11), due_date # june 11th assert_equal Time.zone.local(2008,6,11), due_date # june 11th
# same month, after second wednesday # same month, after second wednesday
due_date = @yearly.get_due_date(Time.zone.local(2008,6,12)) # june 7th due_date = @yearly.get_due_date(Time.zone.local(2008,6,12)) # june 7th
assert_equal Time.zone.local(2009,6,10), due_date # june 10th assert_equal Time.zone.local(2009,6,10), due_date # june 10th
# test handling of nil # test handling of nil
due_date1 = @yearly.get_due_date(nil) due_date1 = @yearly.get_due_date(nil)
due_date2 = @yearly.get_due_date(Time.now.utc + 1.day) due_date2 = @yearly.get_due_date(Time.now.utc + 1.day)
assert_equal due_date1, due_date2 assert_equal due_date1, due_date2
end end
def test_last_sunday_of_march def test_last_sunday_of_march
@yearly.recurrence_selector = 1 @yearly.recurrence_selector = 1
@yearly.every_other2 = 3 # march @yearly.every_other2 = 3 # march
@yearly.every_other3 = 5 # last @yearly.every_other3 = 5 # last
@yearly.every_count = 0 # sunday @yearly.every_count = 0 # sunday
due_date = @yearly.get_due_date(Time.zone.local(2008,10,1)) # oct 1st due_date = @yearly.get_due_date(Time.zone.local(2008,10,1)) # oct 1st
assert_equal Time.zone.local(2009,3,29), due_date # march 29th assert_equal Time.zone.local(2009,3,29), due_date # march 29th
end end
def test_start_from_in_future def test_start_from_in_future
# every_day should return start_day if it is in the future # every_day should return start_day if it is in the future
@every_day.start_from = @in_three_days @every_day.start_from = @in_three_days
due_date = @every_day.get_due_date(nil) due_date = @every_day.get_due_date(nil)
assert_equal @in_three_days, due_date assert_equal @in_three_days, due_date
due_date = @every_day.get_due_date(@tomorrow) due_date = @every_day.get_due_date(@tomorrow)
assert_equal @in_three_days, due_date assert_equal @in_three_days, due_date
# if we give a date in the future for the previous todo, the next to do # if we give a date in the future for the previous todo, the next to do
# should be based on that future date. # should be based on that future date.
due_date = @every_day.get_due_date(@in_four_days) due_date = @every_day.get_due_date(@in_four_days)
assert_equal @in_four_days+1.day, due_date assert_equal @in_four_days+1.day, due_date
@weekly_every_day.start_from = Time.zone.local(2020,1,1) @weekly_every_day.start_from = Time.zone.local(2020,1,1)
assert_equal Time.zone.local(2020,1,1), @weekly_every_day.get_due_date(nil) assert_equal Time.zone.local(2020,1,1), @weekly_every_day.get_due_date(nil)
assert_equal Time.zone.local(2020,1,1), @weekly_every_day.get_due_date(Time.zone.local(2019,10,1)) assert_equal Time.zone.local(2020,1,1), @weekly_every_day.get_due_date(Time.zone.local(2019,10,1))
assert_equal Time.zone.local(2020,1,10), @weekly_every_day.get_due_date(Time.zone.local(2020,1,9)) assert_equal Time.zone.local(2020,1,10), @weekly_every_day.get_due_date(Time.zone.local(2020,1,9))
@monthly_every_last_friday.start_from = Time.zone.local(2020,1,1) @monthly_every_last_friday.start_from = Time.zone.local(2020,1,1)
assert_equal Time.zone.local(2020,1,31), @monthly_every_last_friday.get_due_date(nil) # last friday of jan assert_equal Time.zone.local(2020,1,31), @monthly_every_last_friday.get_due_date(nil) # last friday of jan
assert_equal Time.zone.local(2020,1,31), @monthly_every_last_friday.get_due_date(Time.zone.local(2019,12,1)) # last friday of jan assert_equal Time.zone.local(2020,1,31), @monthly_every_last_friday.get_due_date(Time.zone.local(2019,12,1)) # last friday of jan
assert_equal Time.zone.local(2020,2,28), @monthly_every_last_friday.get_due_date(Time.zone.local(2020,2,1)) # last friday of feb assert_equal Time.zone.local(2020,2,28), @monthly_every_last_friday.get_due_date(Time.zone.local(2020,2,1)) # last friday of feb
# start from after june 8th 2008 # start from after june 8th 2008
@yearly.start_from = Time.zone.local(2020,6,12) @yearly.start_from = Time.zone.local(2020,6,12)
assert_equal Time.zone.local(2021,6,8), @yearly.get_due_date(nil) # jun 8th next year assert_equal Time.zone.local(2021,6,8), @yearly.get_due_date(nil) # jun 8th next year
assert_equal Time.zone.local(2021,6,8), @yearly.get_due_date(Time.zone.local(2019,6,1)) # also next year assert_equal Time.zone.local(2021,6,8), @yearly.get_due_date(Time.zone.local(2019,6,1)) # also next year
assert_equal Time.zone.local(2021,6,8), @yearly.get_due_date(Time.zone.local(2020,6,15)) # also next year assert_equal Time.zone.local(2021,6,8), @yearly.get_due_date(Time.zone.local(2020,6,15)) # also next year
this_year = Time.now.utc.year this_year = Time.now.utc.year
@yearly.start_from = Time.zone.local(this_year+1,6,12) @yearly.start_from = Time.zone.local(this_year+1,6,12)
due_date = @yearly.get_due_date(nil) due_date = @yearly.get_due_date(nil)
assert_equal due_date.year, this_year+2 assert_equal due_date.year, this_year+2
end end
def test_toggle_completion def test_toggle_completion
t = @yearly t = @yearly
assert_equal :active, t.current_state assert_equal :active, t.current_state
t.toggle_completion! t.toggle_completion!
assert_equal :completed, t.current_state assert_equal :completed, t.current_state
t.toggle_completion! t.toggle_completion!
assert_equal :active, t.current_state assert_equal :active, t.current_state
end end
def test_starred def test_starred
@yearly.tag_with("1, 2, starred", User.find(@yearly.user_id)) @yearly.tag_with("1, 2, starred")
@yearly.tags.reload @yearly.tags.reload
assert_equal true, @yearly.starred? assert_equal true, @yearly.starred?
assert_equal false, @weekly_every_day.starred? assert_equal false, @weekly_every_day.starred?
@yearly.toggle_star! @yearly.toggle_star!
assert_equal false, @yearly.starred? assert_equal false, @yearly.starred?
@yearly.toggle_star! @yearly.toggle_star!
assert_equal true, @yearly.starred? assert_equal true, @yearly.starred?
end end
def test_occurence_count def test_occurence_count
@every_day.number_of_occurences = 2 @every_day.number_of_occurences = 2
assert_equal true, @every_day.has_next_todo(@in_three_days) assert_equal true, @every_day.has_next_todo(@in_three_days)
@every_day.inc_occurences @every_day.inc_occurences
assert_equal true, @every_day.has_next_todo(@in_three_days) assert_equal true, @every_day.has_next_todo(@in_three_days)
@every_day.inc_occurences @every_day.inc_occurences
assert_equal false, @every_day.has_next_todo(@in_three_days) assert_equal false, @every_day.has_next_todo(@in_three_days)
# after completion, when you reactivate the recurring todo, the occurences # after completion, when you reactivate the recurring todo, the occurences
# count should be reset # count should be reset
assert_equal 2, @every_day.occurences_count assert_equal 2, @every_day.occurences_count
@every_day.toggle_completion! @every_day.toggle_completion!
@every_day.toggle_completion! @every_day.toggle_completion!
assert_equal true, @every_day.has_next_todo(@in_three_days) assert_equal true, @every_day.has_next_todo(@in_three_days)
assert_equal 0, @every_day.occurences_count assert_equal 0, @every_day.occurences_count
end end
end end

View file

@ -4,7 +4,26 @@ class TagTest < Test::Rails::TestCase
fixtures :tags fixtures :tags
# Replace this with your real tests. # Replace this with your real tests.
def test_truth def test_find_or_create_with_single_word
assert true tag = Tag.find_or_create_by_name("test")
assert !tag.new_record?
end
def test_find_or_create_with_space
tag = Tag.find_or_create_by_name("test test")
assert !tag.new_record?
end
def test_find_or_create_with_dot
tag = Tag.find_or_create_by_name("a.b.c")
assert !tag.new_record?
end
def test_find_or_create_with_number_as_string
tag = Tag.find_or_create_by_name("12343")
assert !tag.new_record?
tag = Tag.find_or_create_by_name("8.1.2")
assert !tag.new_record?
end end
end end

View file

@ -1,181 +1,181 @@
require File.dirname(__FILE__) + '/../test_helper' require File.dirname(__FILE__) + '/../test_helper'
require 'date' require 'date'
class TodoTest < Test::Rails::TestCase class TodoTest < Test::Rails::TestCase
fixtures :todos, :users, :contexts, :preferences, :tags, :taggings fixtures :todos, :users, :contexts, :preferences, :tags, :taggings
def setup def setup
@not_completed1 = Todo.find(1).reload @not_completed1 = Todo.find(1).reload
@not_completed2 = Todo.find(2).reload @not_completed2 = Todo.find(2).reload
@completed = Todo.find(8).reload @completed = Todo.find(8).reload
end end
# Test loading a todo item # Test loading a todo item
def test_load def test_load
assert_kind_of Todo, @not_completed1 assert_kind_of Todo, @not_completed1
assert_equal 1, @not_completed1.id assert_equal 1, @not_completed1.id
assert_equal 1, @not_completed1.context_id assert_equal 1, @not_completed1.context_id
assert_equal 2, @not_completed1.project_id 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_equal "Call Bill Gates to find out how much he makes per day", @not_completed1.description
assert_nil @not_completed1.notes assert_nil @not_completed1.notes
assert @not_completed1.completed? == false assert @not_completed1.completed? == false
assert_equal 1.week.ago.beginning_of_day.strftime("%Y-%m-%d %H:%M"), @not_completed1.created_at.strftime("%Y-%m-%d %H:%M") assert_equal 1.week.ago.beginning_of_day.strftime("%Y-%m-%d %H:%M"), @not_completed1.created_at.strftime("%Y-%m-%d %H:%M")
assert_equal 2.week.from_now.beginning_of_day.strftime("%Y-%m-%d"), @not_completed1.due.strftime("%Y-%m-%d") assert_equal 2.week.from_now.beginning_of_day.strftime("%Y-%m-%d"), @not_completed1.due.strftime("%Y-%m-%d")
assert_nil @not_completed1.completed_at assert_nil @not_completed1.completed_at
assert_equal 1, @not_completed1.user_id assert_equal 1, @not_completed1.user_id
end end
def test_completed def test_completed
assert_kind_of Todo, @completed assert_kind_of Todo, @completed
assert @completed.completed? assert @completed.completed?
assert_not_nil @completed.completed_at assert_not_nil @completed.completed_at
end end
def test_completed_at_cleared_after_toggle_to_active def test_completed_at_cleared_after_toggle_to_active
assert_kind_of Todo, @completed assert_kind_of Todo, @completed
assert @completed.completed? assert @completed.completed?
@completed.toggle_completion! @completed.toggle_completion!
assert @completed.active? assert @completed.active?
assert_nil @completed.completed_at assert_nil @completed.completed_at
end end
# Validation tests # Validation tests
# #
def test_validate_presence_of_description def test_validate_presence_of_description
assert_equal "Call dinosaur exterminator", @not_completed2.description assert_equal "Call dinosaur exterminator", @not_completed2.description
@not_completed2.description = "" @not_completed2.description = ""
assert !@not_completed2.save assert !@not_completed2.save
assert_equal 1, @not_completed2.errors.count assert_equal 1, @not_completed2.errors.count
assert_equal "can't be blank", @not_completed2.errors.on(:description) assert_equal "can't be blank", @not_completed2.errors.on(:description)
end end
def test_validate_length_of_description def test_validate_length_of_description
assert_equal "Call dinosaur exterminator", @not_completed2.description assert_equal "Call dinosaur exterminator", @not_completed2.description
@not_completed2.description = generate_random_string(101) @not_completed2.description = generate_random_string(101)
assert !@not_completed2.save assert !@not_completed2.save
assert_equal 1, @not_completed2.errors.count assert_equal 1, @not_completed2.errors.count
assert_equal "is too long (maximum is 100 characters)", @not_completed2.errors.on(:description) assert_equal "is too long (maximum is 100 characters)", @not_completed2.errors.on(:description)
end end
def test_validate_length_of_notes def test_validate_length_of_notes
assert_equal "Ask him if I need to hire a skip for the corpses.", @not_completed2.notes assert_equal "Ask him if I need to hire a skip for the corpses.", @not_completed2.notes
@not_completed2.notes = generate_random_string(60001) @not_completed2.notes = generate_random_string(60001)
assert !@not_completed2.save assert !@not_completed2.save
assert_equal 1, @not_completed2.errors.count assert_equal 1, @not_completed2.errors.count
assert_equal "is too long (maximum is 60000 characters)", @not_completed2.errors.on(:notes) assert_equal "is too long (maximum is 60000 characters)", @not_completed2.errors.on(:notes)
end end
def test_validate_show_from_must_be_a_date_in_the_future def test_validate_show_from_must_be_a_date_in_the_future
t = @not_completed2 t = @not_completed2
t[:show_from] = 1.week.ago # we have to set this via the indexer because show_from=() updates the state t[:show_from] = 1.week.ago # we have to set this via the indexer because show_from=() updates the state
# and actual show_from value appropriately based on the date # and actual show_from value appropriately based on the date
assert !t.save assert !t.save
assert_equal 1, t.errors.count assert_equal 1, t.errors.count
assert_equal "must be a date in the future", t.errors.on(:show_from) assert_equal "must be a date in the future", t.errors.on(:show_from)
end end
def test_defer_an_existing_todo def test_defer_an_existing_todo
@not_completed2 @not_completed2
assert_equal :active, @not_completed2.current_state assert_equal :active, @not_completed2.current_state
@not_completed2.show_from = next_week @not_completed2.show_from = next_week
assert @not_completed2.save, "should have saved successfully" + @not_completed2.errors.to_xml assert @not_completed2.save, "should have saved successfully" + @not_completed2.errors.to_xml
assert_equal :deferred, @not_completed2.current_state assert_equal :deferred, @not_completed2.current_state
end end
def test_create_a_new_deferred_todo def test_create_a_new_deferred_todo
user = users(:other_user) user = users(:other_user)
todo = user.todos.build todo = user.todos.build
todo.show_from = next_week todo.show_from = next_week
todo.context_id = 1 todo.context_id = 1
todo.description = 'foo' todo.description = 'foo'
assert todo.save, "should have saved successfully" + todo.errors.to_xml assert todo.save, "should have saved successfully" + todo.errors.to_xml
assert_equal :deferred, todo.current_state assert_equal :deferred, todo.current_state
end end
def test_create_a_new_deferred_todo_by_passing_attributes def test_create_a_new_deferred_todo_by_passing_attributes
user = users(:other_user) user = users(:other_user)
todo = user.todos.build(:show_from => next_week, :context_id => 1, :description => 'foo') todo = user.todos.build(:show_from => next_week, :context_id => 1, :description => 'foo')
assert todo.save, "should have saved successfully" + todo.errors.to_xml assert todo.save, "should have saved successfully" + todo.errors.to_xml
assert_equal :deferred, todo.current_state assert_equal :deferred, todo.current_state
end end
def test_feed_options def test_feed_options
opts = Todo.feed_options(users(:admin_user)) opts = Todo.feed_options(users(:admin_user))
assert_equal 'Tracks Actions', opts[:title], 'Unexpected value for :title key of feed_options' assert_equal 'Tracks Actions', opts[:title], 'Unexpected value for :title key of feed_options'
assert_equal 'Actions for Admin Schmadmin', opts[:description], 'Unexpected value for :description key of feed_options' assert_equal 'Actions for Admin Schmadmin', opts[:description], 'Unexpected value for :description key of feed_options'
end end
def test_toggle_completion def test_toggle_completion
t = @not_completed1 t = @not_completed1
assert_equal :active, t.current_state assert_equal :active, t.current_state
t.toggle_completion! t.toggle_completion!
assert_equal :completed, t.current_state assert_equal :completed, t.current_state
t.toggle_completion! t.toggle_completion!
assert_equal :active, t.current_state assert_equal :active, t.current_state
end end
def test_activate_also_saves def test_activate_also_saves
t = @not_completed1 t = @not_completed1
t.show_from = 1.week.from_now t.show_from = 1.week.from_now
t.save! t.save!
assert t.deferred? assert t.deferred?
t.reload t.reload
t.activate! t.activate!
assert t.active? assert t.active?
t.reload t.reload
assert t.active? assert t.active?
end end
def test_project_returns_null_object_when_nil def test_project_returns_null_object_when_nil
t = @not_completed1 t = @not_completed1
assert !t.project.is_a?(NullProject) assert !t.project.is_a?(NullProject)
t.project = nil t.project = nil
assert t.project.is_a?(NullProject) assert t.project.is_a?(NullProject)
end end
def test_initial_state_defaults_to_active def test_initial_state_defaults_to_active
t = Todo.new t = Todo.new
t.description = 'foo' t.description = 'foo'
t.context_id = 1 t.context_id = 1
t.save! t.save!
t.reload t.reload
assert_equal :active, t.current_state assert_equal :active, t.current_state
end end
def test_initial_state_is_deferred_when_show_from_in_future def test_initial_state_is_deferred_when_show_from_in_future
t = Todo.new t = Todo.new
t.user = users(:admin_user) t.user = users(:admin_user)
t.description = 'foo' t.description = 'foo'
t.context_id = 1 t.context_id = 1
t.show_from = 1.week.from_now.to_date t.show_from = 1.week.from_now.to_date
t.save! t.save!
t.reload t.reload
assert_equal :deferred, t.current_state assert_equal :deferred, t.current_state
end end
def test_todo_is_not_starred def test_todo_is_not_starred
assert !@not_completed1.starred? assert !@not_completed1.starred?
end end
def test_todo_2_is_not_starred def test_todo_2_is_not_starred
assert !Todo.find(2).starred? assert !Todo.find(2).starred?
end end
def test_todo_is_starred_after_starred_tag_is_added def test_todo_is_starred_after_starred_tag_is_added
@not_completed1.add_tag('starred') @not_completed1._add_tags('starred')
assert @not_completed1.starred? assert @not_completed1.starred?
end end
def test_todo_is_starred_after_toggle_starred def test_todo_is_starred_after_toggle_starred
@not_completed1.toggle_star! @not_completed1.toggle_star!
assert @not_completed1.starred? assert @not_completed1.starred?
end end
def test_todo_is_not_starred_after_toggle_starred_twice def test_todo_is_not_starred_after_toggle_starred_twice
@not_completed1.toggle_star! @not_completed1.toggle_star!
@not_completed1.toggle_star! @not_completed1.toggle_star!
assert !@not_completed1.starred? assert !@not_completed1.starred?
end end
end end

View file

@ -0,0 +1,87 @@
--- !ruby/object:Gem::Specification
name: highline
version: !ruby/object:Gem::Version
version: 1.5.0
platform: ruby
authors:
- James Edward Gray II
autorequire:
bindir: bin
cert_chain: []
date: 2008-11-05 00:00:00 +01:00
default_executable:
dependencies: []
description: A high-level IO library that provides validation, type conversion, and more for command-line interfaces. HighLine also includes a complete menu system that can crank out anything from simple list selection to complete shells with just minutes of work.
email: james@grayproductions.net
executables: []
extensions: []
extra_rdoc_files:
- README
- INSTALL
- TODO
- CHANGELOG
- LICENSE
files:
- examples/ansi_colors.rb
- examples/asking_for_arrays.rb
- examples/basic_usage.rb
- examples/color_scheme.rb
- examples/menus.rb
- examples/overwrite.rb
- examples/page_and_wrap.rb
- examples/password.rb
- examples/trapping_eof.rb
- examples/using_readline.rb
- lib/highline/color_scheme.rb
- lib/highline/import.rb
- lib/highline/menu.rb
- lib/highline/question.rb
- lib/highline/system_extensions.rb
- lib/highline.rb
- test/tc_color_scheme.rb
- test/tc_highline.rb
- test/tc_import.rb
- test/tc_menu.rb
- test/ts_all.rb
- Rakefile
- setup.rb
- README
- INSTALL
- TODO
- CHANGELOG
- LICENSE
has_rdoc: true
homepage: http://highline.rubyforge.org
post_install_message:
rdoc_options:
- --title
- HighLine Documentation
- --main
- README
require_paths:
- lib
required_ruby_version: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: "0"
version:
required_rubygems_version: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: "0"
version:
requirements: []
rubyforge_project: highline
rubygems_version: 1.3.1
signing_key:
specification_version: 2
summary: HighLine is a high-level command-line IO library.
test_files:
- test/ts_all.rb

View file

@ -8,7 +8,7 @@ authors:
autorequire: openid autorequire: openid
bindir: bin bindir: bin
cert_chain: cert_chain:
date: 2008-06-27 00:00:00 -04:00 date: 2008-06-27 05:00:00 +01:00
default_executable: default_executable:
dependencies: [] dependencies: []
@ -265,6 +265,7 @@ rdoc_options:
- --main - --main
- README - README
require_paths: require_paths:
- bin
- lib - lib
required_ruby_version: !ruby/object:Gem::Requirement required_ruby_version: !ruby/object:Gem::Requirement
requirements: requirements:
@ -281,7 +282,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
requirements: [] requirements: []
rubyforge_project: rubyforge_project:
rubygems_version: 1.2.0 rubygems_version: 1.0.1
signing_key: signing_key:
specification_version: 1 specification_version: 1
summary: A library for consuming and serving OpenID identities. summary: A library for consuming and serving OpenID identities.

View file

@ -120,7 +120,7 @@ class ActiveRecord::Base #:nodoc:
sql = "SELECT #{(scope && scope[:select]) || options[:select]} " sql = "SELECT #{(scope && scope[:select]) || options[:select]} "
sql << "FROM #{(scope && scope[:from]) || options[:from]} " sql << "FROM #{(scope && scope[:from]) || options[:from]} "
add_joins!(sql, options, scope) add_joins!(sql, options[:joins], scope)
sql << "WHERE #{table_name}.#{primary_key} = taggings.taggable_id " sql << "WHERE #{table_name}.#{primary_key} = taggings.taggable_id "
sql << "AND taggings.taggable_type = '#{ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s}' " sql << "AND taggings.taggable_type = '#{ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s}' "

View file

@ -360,10 +360,14 @@ Be aware, however, that <tt>NULL != 'Spot'</tt> returns <tt>false</tt> due to SQ
begin begin
table = plural._as_class.table_name table = plural._as_class.table_name
rescue NameError => e rescue NameError => e
raise PolymorphicError, "Could not find a valid class for #{plural.inspect}. If it's namespaced, be sure to specify it as :\"module/#{plural}\" instead." raise PolymorphicError, "Could not find a valid class for #{plural.inspect} (tried #{plural.to_s._classify}). If it's namespaced, be sure to specify it as :\"module/#{plural}\" instead."
end end
plural._as_class.columns.map(&:name).each_with_index do |field, f_index| begin
aliases["#{table}.#{field}"] = "t#{t_index}_r#{f_index}" plural._as_class.columns.map(&:name).each_with_index do |field, f_index|
aliases["#{table}.#{field}"] = "t#{t_index}_r#{f_index}"
end
rescue ActiveRecord::StatementInvalid => e
_logger_warn "Looks like your table doesn't exist for #{plural.to_s._classify}.\nError #{e}\nSkipping..."
end end
end end
end end

View file

@ -33,7 +33,7 @@ Inherits from ActiveRecord::Reflection::AssociationReflection.
=end =end
class PolymorphicReflection < AssociationReflection class PolymorphicReflection < ThroughReflection
# Stub out the validity check. Has_many_polymorphs checks validity on macro creation, not on reflection. # Stub out the validity check. Has_many_polymorphs checks validity on macro creation, not on reflection.
def check_validity! def check_validity!
# nothing # nothing

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file