mirror of
https://github.com/TracksApp/tracks.git
synced 2025-12-29 05:18:49 +01:00
Merge branch 'master' of git://github.com/bsag/tracks
This commit is contained in:
commit
122426ab40
74 changed files with 1431 additions and 251 deletions
|
|
@ -16,6 +16,8 @@ require 'time'
|
|||
#
|
||||
# Tag
|
||||
|
||||
class CannotAccessContext < RuntimeError; end
|
||||
|
||||
class ApplicationController < ActionController::Base
|
||||
|
||||
protect_from_forgery :secret => SALT
|
||||
|
|
@ -173,7 +175,7 @@ class ApplicationController < ActionController::Base
|
|||
if show_from_date.nil?
|
||||
todo.show_from=nil
|
||||
else
|
||||
todo.show_from = show_from_date.to_time < Time.now.utc ? nil : show_from_date
|
||||
todo.show_from = show_from_date < Time.zone.now ? nil : show_from_date
|
||||
end
|
||||
|
||||
saved = todo.save
|
||||
|
|
|
|||
|
|
@ -14,36 +14,10 @@ class BackendController < ApplicationController
|
|||
|
||||
def new_rich_todo(username, token, default_context_id, description, notes)
|
||||
check_token(username,token)
|
||||
description,context = split_by_char('@',description)
|
||||
description,project = split_by_char('>',description)
|
||||
if(!context.nil? && project.nil?)
|
||||
context,project = split_by_char('>',context)
|
||||
end
|
||||
# logger.info("context='#{context}' project='#{project}")
|
||||
|
||||
context_id = default_context_id
|
||||
unless(context.nil?)
|
||||
found_context = @user.active_contexts.find_by_namepart(context)
|
||||
found_context = @user.contexts.find_by_namepart(context) if found_context.nil?
|
||||
context_id = found_context.id unless found_context.nil?
|
||||
end
|
||||
check_context_belongs_to_user(context_id)
|
||||
|
||||
project_id = nil
|
||||
unless(project.blank?)
|
||||
if(project[0..3].downcase == "new:")
|
||||
found_project = @user.projects.build
|
||||
found_project.name = project[4..255+4].strip
|
||||
found_project.save!
|
||||
else
|
||||
found_project = @user.active_projects.find_by_namepart(project)
|
||||
found_project = @user.projects.find_by_namepart(project) if found_project.nil?
|
||||
end
|
||||
project_id = found_project.id unless found_project.nil?
|
||||
end
|
||||
|
||||
todo = create_todo(description, context_id, project_id, notes)
|
||||
todo.id
|
||||
item = Todo.from_rich_message(@user, default_context_id, description, notes)
|
||||
item.save
|
||||
raise item.errors.full_messages.to_s if item.new_record?
|
||||
item.id
|
||||
end
|
||||
|
||||
def list_contexts(username, token)
|
||||
|
|
@ -84,25 +58,6 @@ class BackendController < ApplicationController
|
|||
raise item.errors.full_messages.to_s if item.new_record?
|
||||
item
|
||||
end
|
||||
|
||||
def split_by_char(separator,string)
|
||||
parts = string.split(separator)
|
||||
|
||||
# if the separator is used more than once, concat the last parts this is
|
||||
# needed to get 'description @ @home > project' working for contexts
|
||||
# starting with @
|
||||
if parts.length > 2
|
||||
2.upto(parts.length-1) { |i| parts[1] += (separator +parts[i]) }
|
||||
end
|
||||
|
||||
return safe_strip(parts[0]), safe_strip(parts[1])
|
||||
end
|
||||
|
||||
def safe_strip(s)
|
||||
s.strip! unless s.nil?
|
||||
s
|
||||
end
|
||||
end
|
||||
|
||||
class InvalidToken < RuntimeError; end
|
||||
class CannotAccessContext < RuntimeError; end
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ class DataController < ApplicationController
|
|||
require 'csv'
|
||||
|
||||
def index
|
||||
@page_title = "TRACKS::Export"
|
||||
@page_title = "TRACKS::Export"
|
||||
end
|
||||
|
||||
def import
|
||||
|
|
@ -24,6 +24,7 @@ class DataController < ApplicationController
|
|||
all_tables['tags'] = current_user.tags.find(:all)
|
||||
all_tables['taggings'] = current_user.taggings.find(:all)
|
||||
all_tables['notes'] = current_user.notes.find(:all)
|
||||
all_tables['recurring_todos'] = current_user.recurring_todos.find(:all)
|
||||
|
||||
result = all_tables.to_yaml
|
||||
result.gsub!(/\n/, "\r\n") # TODO: general functionality for line endings
|
||||
|
|
@ -34,21 +35,21 @@ class DataController < ApplicationController
|
|||
content_type = 'text/csv'
|
||||
CSV::Writer.generate(result = "") do |csv|
|
||||
csv << ["id", "Context", "Project", "Description", "Notes", "Tags",
|
||||
"Created at", "Due", "Completed at", "User ID", "Show from",
|
||||
"state"]
|
||||
"Created at", "Due", "Completed at", "User ID", "Show from",
|
||||
"state"]
|
||||
current_user.todos.find(:all, :include => [:context, :project]).each do |todo|
|
||||
# Format dates in ISO format for easy sorting in spreadsheet
|
||||
# Print context and project names for easy viewing
|
||||
# Format dates in ISO format for easy sorting in spreadsheet Print
|
||||
# context and project names for easy viewing
|
||||
csv << [todo.id, todo.context.name,
|
||||
todo.project_id = todo.project_id.nil? ? "" : todo.project.name,
|
||||
todo.description,
|
||||
todo.notes, todo.tags.collect{|t| t.name}.join(', '),
|
||||
todo.created_at.to_formatted_s(:db),
|
||||
todo.due = todo.due? ? todo.due.to_formatted_s(:db) : "",
|
||||
todo.completed_at = todo.completed_at? ? todo.completed_at.to_formatted_s(:db) : "",
|
||||
todo.user_id,
|
||||
todo.show_from = todo.show_from? ? todo.show_from.to_formatted_s(:db) : "",
|
||||
todo.state]
|
||||
todo.project_id = todo.project_id.nil? ? "" : todo.project.name,
|
||||
todo.description,
|
||||
todo.notes, todo.tags.collect{|t| t.name}.join(', '),
|
||||
todo.created_at.to_formatted_s(:db),
|
||||
todo.due = todo.due? ? todo.due.to_formatted_s(:db) : "",
|
||||
todo.completed_at = todo.completed_at? ? todo.completed_at.to_formatted_s(:db) : "",
|
||||
todo.user_id,
|
||||
todo.show_from = todo.show_from? ? todo.show_from.to_formatted_s(:db) : "",
|
||||
todo.state]
|
||||
end
|
||||
end
|
||||
send_data(result, :filename => "todos.csv", :type => content_type)
|
||||
|
|
@ -58,16 +59,17 @@ class DataController < ApplicationController
|
|||
content_type = 'text/csv'
|
||||
CSV::Writer.generate(result = "") do |csv|
|
||||
csv << ["id", "User ID", "Project", "Note",
|
||||
"Created at", "Updated at"]
|
||||
# had to remove project include because it's association order is leaking through
|
||||
# and causing an ambiguous column ref even with_exclusive_scope didn't seem to help -JamesKebinger
|
||||
"Created at", "Updated at"]
|
||||
# had to remove project include because it's association order is leaking
|
||||
# through and causing an ambiguous column ref even with_exclusive_scope
|
||||
# didn't seem to help -JamesKebinger
|
||||
current_user.notes.find(:all,:order=>"notes.created_at").each do |note|
|
||||
# Format dates in ISO format for easy sorting in spreadsheet
|
||||
# Print context and project names for easy viewing
|
||||
# Format dates in ISO format for easy sorting in spreadsheet Print
|
||||
# context and project names for easy viewing
|
||||
csv << [note.id, note.user_id,
|
||||
note.project_id = note.project_id.nil? ? "" : note.project.name,
|
||||
note.body, note.created_at.to_formatted_s(:db),
|
||||
note.updated_at.to_formatted_s(:db)]
|
||||
note.project_id = note.project_id.nil? ? "" : note.project.name,
|
||||
note.body, note.created_at.to_formatted_s(:db),
|
||||
note.updated_at.to_formatted_s(:db)]
|
||||
end
|
||||
end
|
||||
send_data(result, :filename => "notes.csv", :type => content_type)
|
||||
|
|
@ -81,6 +83,7 @@ class DataController < ApplicationController
|
|||
result << current_user.tags.find(:all).to_xml(:skip_instruct => true)
|
||||
result << current_user.taggings.find(:all).to_xml(:skip_instruct => true)
|
||||
result << current_user.notes.find(:all).to_xml(:skip_instruct => true)
|
||||
result << current_user.recurring_todos.find(:all).to_xml(:skip_instruct => true)
|
||||
send_data(result, :filename => "tracks_backup.xml", :type => 'text/xml')
|
||||
end
|
||||
|
||||
|
|
@ -88,8 +91,113 @@ class DataController < ApplicationController
|
|||
# Draw the form to input the YAML text data
|
||||
end
|
||||
|
||||
def yaml_import
|
||||
# Logic to load the YAML text file and create new records from data
|
||||
# adjusts time to utc
|
||||
def adjust_time(timestring)
|
||||
if (timestring=='') or ( timestring == nil)
|
||||
return nil
|
||||
else
|
||||
return Time.parse(timestring + 'UTC')
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def yaml_import
|
||||
@errmessage = ''
|
||||
@inarray = YAML::load(params['import']['yaml'])
|
||||
# arrays to handle id translations
|
||||
|
||||
# contexts
|
||||
translate_context = Hash.new
|
||||
translate_context[nil] = nil
|
||||
current_user.contexts.each { |context| context.destroy }
|
||||
@inarray['contexts'].each { | item |
|
||||
newitem = Context.new(item.ivars['attributes'])
|
||||
newitem.user_id = current_user.id
|
||||
newitem.created_at = adjust_time(item.ivars['attributes']['created_at'])
|
||||
newitem.save(false)
|
||||
translate_context[item.ivars['attributes']['id'].to_i] = newitem.id
|
||||
}
|
||||
|
||||
# projects
|
||||
translate_project = Hash.new
|
||||
translate_project[nil] = nil
|
||||
current_user.projects.each { |item| item.destroy }
|
||||
@inarray['projects'].each { |item|
|
||||
newitem = Project.new(item.ivars['attributes'])
|
||||
# ids
|
||||
newitem.user_id = current_user.id
|
||||
newitem.default_context_id = translate_context[newitem.default_context_id]
|
||||
newitem.save(false)
|
||||
translate_project[item.ivars['attributes']['id'].to_i] = newitem.id
|
||||
|
||||
# state + dates
|
||||
newitem.transition_to(item.ivars['attributes']['state'])
|
||||
newitem.completed_at = adjust_time(item.ivars['attributes']['completed_at'])
|
||||
newitem.created_at = adjust_time(item.ivars['attributes']['created_at'])
|
||||
newitem.position = item.ivars['attributes']['position']
|
||||
newitem.save(false)
|
||||
}
|
||||
|
||||
# todos
|
||||
translate_todo = Hash.new
|
||||
translate_todo[nil] = nil
|
||||
current_user.todos.each { |item| item.destroy }
|
||||
@inarray['todos'].each { |item|
|
||||
newitem = Todo.new(item.ivars['attributes'])
|
||||
# ids
|
||||
newitem.user_id = current_user.id
|
||||
newitem.context_id = translate_context[newitem.context_id]
|
||||
newitem.project_id = translate_project[newitem.project_id]
|
||||
# TODO: vyresit recurring_todo_id
|
||||
newitem.save(false)
|
||||
translate_todo[item.ivars['attributes']['id'].to_i] = newitem.id
|
||||
|
||||
# state + dates
|
||||
case item.ivars['attributes']['state']
|
||||
when 'active' then newitem.activate!
|
||||
when 'project_hidden' then newitem.hide!
|
||||
when 'completed'
|
||||
newitem.complete!
|
||||
newitem.completed_at = adjust_time(item.ivars['attributes']['completed_at'])
|
||||
when 'deferred' then newitem.defer!
|
||||
end
|
||||
newitem.created_at = adjust_time(item.ivars['attributes']['created_at'])
|
||||
newitem.save(false)
|
||||
}
|
||||
|
||||
# tags
|
||||
translate_tag = Hash.new
|
||||
translate_tag[nil] = nil
|
||||
current_user.tags.each { |item| item.destroy }
|
||||
@inarray['tags'].each { |item|
|
||||
newitem = Tag.new(item.ivars['attributes'])
|
||||
newitem.created_at = adjust_time(item.ivars['attributes']['created_at'])
|
||||
newitem.save
|
||||
translate_tag[item.ivars['attributes']['id'].to_i] = newitem.id
|
||||
}
|
||||
|
||||
# taggings
|
||||
current_user.taggings.each { |item| item.destroy }
|
||||
@inarray['taggings'].each { |item|
|
||||
newitem = Tagging.new(item.ivars['attributes'])
|
||||
newitem.user_id = current_user.id
|
||||
newitem.tag_id = translate_tag[newitem.tag_id]
|
||||
case newitem.taggable_type
|
||||
when 'Todo' then newitem.taggable_id = translate_todo[newitem.taggable_id]
|
||||
else newitem.taggable_id = 0
|
||||
end
|
||||
newitem.save
|
||||
}
|
||||
|
||||
# notes
|
||||
current_user.notes.each { |item| item.destroy }
|
||||
@inarray['notes'].each { |item|
|
||||
newitem = Note.new(item.ivars['attributes'])
|
||||
newitem.id = item.ivars['attributes']['id']
|
||||
newitem.user_id = current_user.id
|
||||
newitem.project_id = translate_project[newitem.project_id]
|
||||
newitem.created_at = adjust_time(item.ivars['attributes']['created_at'])
|
||||
newitem.save
|
||||
}
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -2,9 +2,9 @@ class TodosController < ApplicationController
|
|||
|
||||
helper :todos
|
||||
|
||||
skip_before_filter :login_required, :only => [:index]
|
||||
prepend_before_filter :login_or_feed_token_required, :only => [:index]
|
||||
append_before_filter :init, :except => [ :destroy, :completed, :completed_archive, :check_deferred, :toggle_check, :toggle_star, :edit, :update, :create ]
|
||||
skip_before_filter :login_required, :only => [:index, :calendar]
|
||||
prepend_before_filter :login_or_feed_token_required, :only => [:index, :calendar]
|
||||
append_before_filter :init, :except => [ :destroy, :completed, :completed_archive, :check_deferred, :toggle_check, :toggle_star, :edit, :update, :create, :calendar ]
|
||||
append_before_filter :get_todo_from_params, :only => [ :edit, :toggle_check, :toggle_star, :show, :update, :destroy ]
|
||||
|
||||
session :off, :only => :index, :if => Proc.new { |req| is_feed_request(req) }
|
||||
|
|
@ -128,8 +128,10 @@ class TodosController < ApplicationController
|
|||
# Toggles the 'done' status of the action
|
||||
#
|
||||
def toggle_check
|
||||
@source_view = params['_source_view'] || 'todo'
|
||||
@original_item_due = @todo.due
|
||||
@saved = @todo.toggle_completion!
|
||||
|
||||
|
||||
# check if this todo has a related recurring_todo. If so, create next todo
|
||||
check_for_next_todo if @saved
|
||||
|
||||
|
|
@ -139,6 +141,10 @@ class TodosController < ApplicationController
|
|||
determine_remaining_in_context_count(@todo.context_id)
|
||||
determine_down_count
|
||||
determine_completed_count if @todo.completed?
|
||||
if source_view_is :calendar
|
||||
@original_item_due_id = get_due_id_for_calendar(@original_item_due)
|
||||
@old_due_empty = is_old_due_empty(@original_item_due_id)
|
||||
end
|
||||
end
|
||||
render
|
||||
end
|
||||
|
|
@ -172,6 +178,9 @@ class TodosController < ApplicationController
|
|||
@original_item_context_id = @todo.context_id
|
||||
@original_item_project_id = @todo.project_id
|
||||
@original_item_was_deferred = @todo.deferred?
|
||||
@original_item_due = @todo.due
|
||||
@original_item_due_id = get_due_id_for_calendar(@todo.due)
|
||||
|
||||
if params['todo']['project_id'].blank? && !params['project_name'].nil?
|
||||
if params['project_name'] == 'None'
|
||||
project = Project.null_object
|
||||
|
|
@ -221,7 +230,26 @@ class TodosController < ApplicationController
|
|||
@saved = @todo.update_attributes params["todo"]
|
||||
@context_changed = @original_item_context_id != @todo.context_id
|
||||
@todo_was_activated_from_deferred_state = @original_item_was_deferred && @todo.active?
|
||||
determine_remaining_in_context_count(@original_item_context_id) if @context_changed
|
||||
|
||||
if source_view_is :calendar
|
||||
@due_date_changed = @original_item_due != @todo.due
|
||||
if @due_date_changed
|
||||
@old_due_empty = is_old_due_empty(@original_item_due_id)
|
||||
if @todo.due.nil?
|
||||
# do not act further on date change when date is changed to nil
|
||||
@due_date_changed = false
|
||||
else
|
||||
@new_due_id = get_due_id_for_calendar(@todo.due)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if @context_changed
|
||||
determine_remaining_in_context_count(@original_item_context_id)
|
||||
else
|
||||
determine_remaining_in_context_count(@todo.context_id)
|
||||
end
|
||||
|
||||
@project_changed = @original_item_project_id != @todo.project_id
|
||||
if (@project_changed && !@original_item_project_id.nil?) then @remaining_undone_in_project = current_user.projects.find(@original_item_project_id).not_done_todo_count; end
|
||||
determine_down_count
|
||||
|
|
@ -245,6 +273,7 @@ class TodosController < ApplicationController
|
|||
|
||||
def destroy
|
||||
@todo = get_todo_from_params
|
||||
@original_item_due = @todo.due
|
||||
@context_id = @todo.context_id
|
||||
@project_id = @todo.project_id
|
||||
|
||||
|
|
@ -270,6 +299,9 @@ class TodosController < ApplicationController
|
|||
determine_down_count
|
||||
if source_view_is_one_of(:todo, :deferred)
|
||||
determine_remaining_in_context_count(@context_id)
|
||||
elsif source_view_is :calendar
|
||||
@original_item_due_id = get_due_id_for_calendar(@original_item_due)
|
||||
@old_due_empty = is_old_due_empty(@original_item_due_id)
|
||||
end
|
||||
end
|
||||
render
|
||||
|
|
@ -384,7 +416,63 @@ class TodosController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
private
|
||||
def defer
|
||||
@source_view = params['_source_view'] || 'todo'
|
||||
numdays = params['days'].to_i
|
||||
@todo = Todo.find(params[:id])
|
||||
@todo.show_from = (@todo.show_from || @todo.user.date) + numdays.days
|
||||
@saved = @todo.save
|
||||
|
||||
determine_down_count
|
||||
determine_remaining_in_context_count(@todo.context_id)
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :back }
|
||||
format.js {render :action => 'update'}
|
||||
end
|
||||
end
|
||||
|
||||
def calendar
|
||||
@source_view = params['_source_view'] || 'calendar'
|
||||
@page_title = "TRACKS::Calendar"
|
||||
|
||||
due_today_date = Time.zone.now
|
||||
due_this_week_date = Time.zone.now.end_of_week
|
||||
due_next_week_date = due_this_week_date + 7.days
|
||||
due_this_month_date = Time.zone.now.end_of_month
|
||||
|
||||
@due_today = current_user.todos.find(:all,
|
||||
:include => [:taggings, :tags],
|
||||
:conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due <= ?', 'active', 'deferred', due_today_date],
|
||||
:order => "due")
|
||||
@due_this_week = current_user.todos.find(:all,
|
||||
:include => [:taggings, :tags],
|
||||
:conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due > ? AND todos.due <= ?', 'active', 'deferred', due_today_date, due_this_week_date],
|
||||
:order => "due")
|
||||
@due_next_week = current_user.todos.find(:all,
|
||||
:include => [:taggings, :tags],
|
||||
:conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due > ? AND todos.due <= ?', 'active', 'deferred', due_this_week_date, due_next_week_date],
|
||||
:order => "due")
|
||||
@due_this_month = current_user.todos.find(:all,
|
||||
:include => [:taggings, :tags],
|
||||
:conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due > ? AND todos.due <= ?', 'active', 'deferred', due_next_week_date, due_this_month_date],
|
||||
:order => "due")
|
||||
@due_after_this_month = current_user.todos.find(:all,
|
||||
:include => [:taggings, :tags],
|
||||
:conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due > ?', 'active', 'deferred', due_this_month_date],
|
||||
:order => "due")
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.ics {
|
||||
@due_all = current_user.todos.find(:all,
|
||||
:conditions => ['(todos.state = ? OR todos.state = ?) AND NOT todos.due IS NULL', 'active', 'deferred'],
|
||||
:order => "due")
|
||||
render :action => 'calendar', :layout => false, :content_type => Mime::ICS
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_todo_from_params
|
||||
@todo = current_user.todos.find(params['id'])
|
||||
|
|
@ -688,14 +776,74 @@ class TodosController < ApplicationController
|
|||
@recurring_todo = nil
|
||||
if @todo.from_recurring_todo?
|
||||
@recurring_todo = current_user.recurring_todos.find(@todo.recurring_todo_id)
|
||||
|
||||
# check for next todo either from the due date or the show_from date
|
||||
date_to_check = @todo.due.nil? ? @todo.show_from : @todo.due
|
||||
date_to_check = Date.today()-1.day if date_to_check.nil?
|
||||
|
||||
# if both due and show_from are nil, check for a next todo with yesterday
|
||||
# as reference point. We pick yesterday so that new todos for today will
|
||||
# be created instead of new todos for tomorrow.
|
||||
date_to_check = Time.zone.now-1.day if date_to_check.nil?
|
||||
|
||||
if @recurring_todo.active? && @recurring_todo.has_next_todo(date_to_check)
|
||||
date = date_to_check >= Date.today() ? date_to_check : Date.today()-1.day
|
||||
|
||||
# shift the reference date to yesterday if date_to_check is furher in
|
||||
# the past. This is to make sure we do not get older todos for overdue
|
||||
# todos. I.e. checking a daily todo that is overdue with 5 days will
|
||||
# create a new todo which is overdue by 4 days if we don't shift the
|
||||
# date. Discard the time part in the compare
|
||||
date = date_to_check.at_midnight >= Time.zone.now.at_midnight ? date_to_check : Time.zone.now-1.day
|
||||
|
||||
@new_recurring_todo = create_todo_from_recurring_todo(@recurring_todo, date)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def get_due_id_for_calendar(due)
|
||||
return "" if due.nil?
|
||||
due_today_date = Time.zone.now
|
||||
due_this_week_date = Time.zone.now.end_of_week
|
||||
due_next_week_date = due_this_week_date + 7.days
|
||||
due_this_month_date = Time.zone.now.end_of_month
|
||||
if due <= due_today_date
|
||||
new_due_id = "due_today"
|
||||
elsif due <= due_this_week_date
|
||||
new_due_id = "due_this_week"
|
||||
elsif due <= due_next_week_date
|
||||
new_due_id = "due_next_week"
|
||||
elsif due <= due_this_month_date
|
||||
new_due_id = "due_this_month"
|
||||
else
|
||||
new_due_id = "due_after_this_month"
|
||||
end
|
||||
return new_due_id
|
||||
end
|
||||
|
||||
def is_old_due_empty(id)
|
||||
due_today_date = Time.zone.now
|
||||
due_this_week_date = Time.zone.now.end_of_week
|
||||
due_next_week_date = due_this_week_date + 7.days
|
||||
due_this_month_date = Time.zone.now.end_of_month
|
||||
case id
|
||||
when "due_today"
|
||||
return 0 == current_user.todos.count(:all,
|
||||
:conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due <= ?', 'active', 'deferred', due_today_date])
|
||||
when "due_this_week"
|
||||
return 0 == current_user.todos.count(:all,
|
||||
:conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due > ? AND todos.due <= ?', 'active', 'deferred', due_today_date, due_this_week_date])
|
||||
when "due_next_week"
|
||||
return 0 == current_user.todos.count(:all,
|
||||
:conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due > ? AND todos.due <= ?', 'active', 'deferred', due_this_week_date, due_next_week_date])
|
||||
when "due_this_month"
|
||||
return 0 == current_user.todos.count(:all,
|
||||
:conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due > ? AND todos.due <= ?', 'active', 'deferred', due_next_week_date, due_this_month_date])
|
||||
when "due_after_this_month"
|
||||
return 0 == current_user.todos.count(:all,
|
||||
:conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due > ?', 'active', 'deferred', due_this_month_date])
|
||||
else
|
||||
raise Exception.new, "unknown due id for calendar: '#{id}'"
|
||||
end
|
||||
end
|
||||
|
||||
class FindConditionBuilder
|
||||
|
||||
|
|
|
|||
|
|
@ -280,4 +280,8 @@ module TodosHelper
|
|||
image_tag("blank.png", :title =>"Star action", :class => class_str)
|
||||
end
|
||||
|
||||
def defer_link(days)
|
||||
link_to_remote image_tag("defer_#{days}.png"), :url => {:controller => 'todos', :action => 'defer', :id => @todo.id, :days => days, :_source_view => (@source_view.underscore.gsub(/\s+/,'_') rescue "")}
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
34
app/models/message_gateway.rb
Normal file
34
app/models/message_gateway.rb
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
class MessageGateway < ActionMailer::Base
|
||||
include ActionView::Helpers::SanitizeHelper
|
||||
def receive(email)
|
||||
user = User.find(:first, :include => [:preference], :conditions => ["preferences.sms_email = ?", email.from[0].strip])
|
||||
if user.nil?
|
||||
user = User.find(:first, :include => [:preference], :conditions => ["preferences.sms_email = ?", email.from[0].strip[1,100]])
|
||||
end
|
||||
return if user.nil?
|
||||
context = user.prefs.sms_context
|
||||
|
||||
description = nil
|
||||
notes = nil
|
||||
|
||||
if email.content_type == "multipart/related"
|
||||
description = sanitize email.subject
|
||||
body_part = email.parts.find{|m| m.content_type == "text/plain"}
|
||||
notes = sanitize body_part.body.strip
|
||||
else
|
||||
if email.subject.empty?
|
||||
description = sanitize email.body.strip
|
||||
notes = nil
|
||||
else
|
||||
description = sanitize email.subject.strip
|
||||
notes = sanitize email.body.strip
|
||||
end
|
||||
end
|
||||
|
||||
# stupid T-Mobile often sends the same message multiple times
|
||||
return if user.todos.find(:first, :conditions => {:description => description})
|
||||
|
||||
todo = Todo.from_rich_message(user, context.id, description, notes)
|
||||
todo.save!
|
||||
end
|
||||
end
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
class Preference < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
belongs_to :sms_context, :class_name => 'Context'
|
||||
|
||||
def self.due_styles
|
||||
{ :due_in_n_days => 0, :due_on => 1}
|
||||
|
|
@ -21,7 +22,7 @@ class Preference < ActiveRecord::Base
|
|||
|
||||
def parse_date(s)
|
||||
return nil if s.blank?
|
||||
Date.strptime(s, date_format)
|
||||
user.at_midnight(Date.strptime(s, date_format))
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -16,7 +16,7 @@ class Project < ActiveRecord::Base
|
|||
|
||||
state :active
|
||||
state :hidden, :enter => :hide_todos, :exit => :unhide_todos
|
||||
state :completed, :enter => Proc.new { |p| p.completed_at = Time.now.utc }, :exit => Proc.new { |p| p.completed_at = nil }
|
||||
state :completed, :enter => Proc.new { |p| p.completed_at = Time.zone.now }, :exit => Proc.new { |p| p.completed_at = nil }
|
||||
|
||||
event :activate do
|
||||
transitions :to => :active, :from => [:hidden, :completed]
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class RecurringTodo < ActiveRecord::Base
|
|||
t[:show_from], t.completed_at = nil, nil
|
||||
t.occurences_count = 0
|
||||
}
|
||||
state :completed, :enter => Proc.new { |t| t.completed_at = Time.now.utc }, :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 }
|
||||
|
||||
validates_presence_of :description
|
||||
validates_length_of :description, :maximum => 100
|
||||
|
|
@ -243,7 +243,7 @@ class RecurringTodo < ActiveRecord::Base
|
|||
if self.recurrence_selector == 0
|
||||
return self.every_other2
|
||||
else
|
||||
return Time.now.month
|
||||
return Time.zone.now.month
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -257,7 +257,7 @@ class RecurringTodo < ActiveRecord::Base
|
|||
if self.recurrence_selector == 1
|
||||
return self.every_other2
|
||||
else
|
||||
return Time.now.month
|
||||
return Time.zone.now.month
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -397,7 +397,7 @@ class RecurringTodo < ActiveRecord::Base
|
|||
|
||||
# determine start
|
||||
if previous.nil?
|
||||
start = self.start_from.nil? ? Time.now.utc : self.start_from
|
||||
start = self.start_from.nil? ? Time.zone.now : self.start_from
|
||||
else
|
||||
# use the next day
|
||||
start = previous + 1.day
|
||||
|
|
@ -429,7 +429,7 @@ class RecurringTodo < ActiveRecord::Base
|
|||
def get_weekly_date(previous)
|
||||
# determine start
|
||||
if previous == nil
|
||||
start = self.start_from.nil? ? Time.now.utc : self.start_from
|
||||
start = self.start_from.nil? ? Time.zone.now : self.start_from
|
||||
else
|
||||
start = previous + 1.day
|
||||
if start.wday() == 0
|
||||
|
|
@ -474,7 +474,7 @@ class RecurringTodo < ActiveRecord::Base
|
|||
start += n.months
|
||||
# go back to day
|
||||
end
|
||||
return Time.utc(start.year, start.month, day)
|
||||
return Time.zone.local(start.year, start.month, day)
|
||||
|
||||
when 1 # relative weekday of a month
|
||||
the_next = get_xth_day_of_month(self.every_other3, self.every_count, start.month, start.year)
|
||||
|
|
@ -496,14 +496,14 @@ class RecurringTodo < ActiveRecord::Base
|
|||
def get_xth_day_of_month(x, weekday, month, year)
|
||||
if x == 5
|
||||
# last -> count backwards
|
||||
last_day = Time.utc(year, month, Time.days_in_month(month))
|
||||
last_day = Time.zone.local(year, month, Time.days_in_month(month))
|
||||
while last_day.wday != weekday
|
||||
last_day -= 1.day
|
||||
end
|
||||
return last_day
|
||||
else
|
||||
# 1-4th -> count upwards
|
||||
start = Time.utc(year,month,1)
|
||||
start = Time.zone.local(year,month,1)
|
||||
n = x
|
||||
while n > 0
|
||||
while start.wday() != weekday
|
||||
|
|
@ -526,14 +526,14 @@ class RecurringTodo < ActiveRecord::Base
|
|||
when 0 # specific day of a specific month
|
||||
# if there is no next month n in this year, search in next year
|
||||
if start.month >= month
|
||||
start = Time.utc(start.year+1, month, 1) if start.day >= day
|
||||
start = Time.utc(start.year, month, 1) if start.day <= day
|
||||
start = Time.zone.local(start.year+1, month, 1) if start.day >= day
|
||||
start = Time.zone.local(start.year, month, 1) if start.day <= day
|
||||
end
|
||||
return Time.utc(start.year, month, day)
|
||||
return Time.zone.local(start.year, month, day)
|
||||
|
||||
when 1 # relative weekday of a specific month
|
||||
# if there is no next month n in this year, search in next year
|
||||
the_next = start.month > month ? Time.utc(start.year+1, month, 1) : start
|
||||
the_next = start.month > month ? Time.zone.local(start.year+1, month, 1) : start
|
||||
|
||||
# get the xth day of the month
|
||||
the_next = get_xth_day_of_month(self.every_other3, self.every_count, month, the_next.year)
|
||||
|
|
@ -602,7 +602,7 @@ class RecurringTodo < ActiveRecord::Base
|
|||
|
||||
def determine_start(previous)
|
||||
if previous.nil?
|
||||
start = self.start_from.nil? ? Time.now.utc : self.start_from
|
||||
start = self.start_from.nil? ? Time.zone.now : self.start_from
|
||||
else
|
||||
start = previous
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class Todo < ActiveRecord::Base
|
|||
# of state completed is not run, see #679
|
||||
state :active, :enter => Proc.new { |t| t[:show_from], t.completed_at = nil, nil }
|
||||
state :project_hidden
|
||||
state :completed, :enter => Proc.new { |t| t.completed_at = Time.now.utc }, :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
|
||||
|
||||
event :defer do
|
||||
|
|
@ -68,6 +68,8 @@ class Todo < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def show_from=(date)
|
||||
# parse Date objects into the proper timezone
|
||||
date = user.at_midnight(date) if (date.is_a? Date)
|
||||
activate! if deferred? && date.blank?
|
||||
defer! if active? && !date.blank? && date > user.date
|
||||
self[:show_from] = date
|
||||
|
|
@ -125,4 +127,46 @@ class Todo < ActiveRecord::Base
|
|||
return self.recurring_todo_id != nil
|
||||
end
|
||||
|
||||
end
|
||||
# Rich Todo API
|
||||
|
||||
def self.from_rich_message(user, default_context_id, description, notes)
|
||||
fields = description.match /([^>@]*)@?([^>]*)>?(.*)/
|
||||
description = fields[1].strip
|
||||
context = fields[2].strip
|
||||
project = fields[3].strip
|
||||
|
||||
context = nil if context == ""
|
||||
project = nil if project == ""
|
||||
|
||||
context_id = default_context_id
|
||||
unless(context.nil?)
|
||||
found_context = user.active_contexts.find_by_namepart(context)
|
||||
found_context = user.contexts.find_by_namepart(context) if found_context.nil?
|
||||
context_id = found_context.id unless found_context.nil?
|
||||
end
|
||||
|
||||
unless user.contexts.exists? context_id
|
||||
raise(CannotAccessContext, "Cannot access a context that does not belong to this user.")
|
||||
end
|
||||
|
||||
project_id = nil
|
||||
unless(project.blank?)
|
||||
if(project[0..3].downcase == "new:")
|
||||
found_project = user.projects.build
|
||||
found_project.name = project[4..255+4].strip
|
||||
found_project.save!
|
||||
else
|
||||
found_project = user.active_projects.find_by_namepart(project)
|
||||
found_project = user.projects.find_by_namepart(project) if found_project.nil?
|
||||
end
|
||||
project_id = found_project.id unless found_project.nil?
|
||||
end
|
||||
|
||||
todo = user.todos.build
|
||||
todo.description = description
|
||||
todo.notes = notes
|
||||
todo.context_id = context_id
|
||||
todo.project_id = project_id unless project_id.nil?
|
||||
return todo
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ class User < ActiveRecord::Base
|
|||
has_many :active_contexts,
|
||||
:class_name => 'Context',
|
||||
:order => 'position ASC',
|
||||
:conditions => [ 'hide = ?', 'true' ]
|
||||
:conditions => [ 'hide = ?', false ]
|
||||
has_many :todos,
|
||||
:order => 'todos.completed_at DESC, todos.created_at DESC',
|
||||
:dependent => :delete_all
|
||||
|
|
@ -71,8 +71,7 @@ class User < ActiveRecord::Base
|
|||
:conditions => [ 'state = ?', 'deferred' ],
|
||||
:order => 'show_from ASC, todos.created_at DESC' do
|
||||
def find_and_activate_ready
|
||||
# assumes that active record uses :utc to store datetime in db
|
||||
find(:all, :conditions => ['show_from <= ?', Time.now.utc ]).collect { |t| t.activate! }
|
||||
find(:all, :conditions => ['show_from <= ?', Time.zone.now ]).collect { |t| t.activate! }
|
||||
end
|
||||
end
|
||||
has_many :completed_todos,
|
||||
|
|
@ -170,7 +169,11 @@ class User < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def date
|
||||
time.to_date
|
||||
time.midnight
|
||||
end
|
||||
|
||||
def at_midnight(date)
|
||||
return TimeZone[prefs.time_zone].local(date.year, date.month, date.day, 0, 0, 0)
|
||||
end
|
||||
|
||||
def generate_token
|
||||
|
|
|
|||
|
|
@ -53,4 +53,9 @@
|
|||
</div>
|
||||
<%
|
||||
sortable_element 'list-contexts', get_listing_sortable_options
|
||||
-%>
|
||||
-%>
|
||||
<script type="text/javascript">
|
||||
window.onload=function(){
|
||||
Nifty("div#context_new_container","normal");
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,37 +1,49 @@
|
|||
<div id="feeds">
|
||||
<div id="feedlegend">
|
||||
<h3>Exporting data</h3>
|
||||
<p>You can choose between the following formats:</p>
|
||||
<ul>
|
||||
<li><strong>YAML: </strong>Best for exporting data. <br/><i>Please note that Tracks currently does not support importing YAML files. Do not use this (yet) to backup your Tracks database.</i></li>
|
||||
<li><strong>CSV: </strong>Best for importing into spreadsheet or data analysis software</li>
|
||||
<li><strong>XML: </strong>Best for importing or repurposing the data</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="display_box">
|
||||
<div id="feeds">
|
||||
<div id="feedlegend">
|
||||
<h3>Importing data</h3>
|
||||
<p>Curently there is a experimental support for importing YAML files.
|
||||
Beware: all your current data will be destroyed before importing the YAML
|
||||
file, so if you have access to the database, we strongly recomment backing up
|
||||
the database right now in case that anything goes wrong.
|
||||
</p>
|
||||
<p><%= link_to "Start import", :controller => 'data', :action => 'yaml_form' %>.</p>
|
||||
<br/><br/><h3>Exporting data</h3>
|
||||
<p>You can choose between the following formats:</p>
|
||||
<ul>
|
||||
<li><strong>YAML: </strong>Best for exporting data. <br/><i>Please note that importing YAML files is currently supported only in experimentally. Do not rely on it for backing up critical data.</i></li>
|
||||
<li><strong>CSV: </strong>Best for importing into spreadsheet or data analysis software</li>
|
||||
<li><strong>XML: </strong>Best for importing or repurposing the data</li>
|
||||
</ul>
|
||||
</div>
|
||||
<br/><br/>
|
||||
<table class="export_table">
|
||||
<tr>
|
||||
<th>Description</th>
|
||||
<th>Download link</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>YAML file containing all your actions, contexts, projects, tags and notes</td>
|
||||
<td><%= link_to "YAML file", :controller => 'data', :action => 'yaml_export' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CSV file containing all of your actions, with named contexts and projects</td>
|
||||
<td><%= link_to "CSV file (actions, contexts and projects)", :controller => 'data', :action => 'csv_actions' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CSV file containing all your notes</td>
|
||||
<td><%= link_to "CSV file (notes only)", :controller => 'data', :action => 'csv_notes' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>XML file containing all your actions, contexts, projects, tags and notes</td>
|
||||
<td><%= link_to "XML file (actions only)", :controller => 'data', :action => 'xml_export' %></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div><!-- End of feeds -->
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<table class="export_table">
|
||||
<tr>
|
||||
<th>Description</th>
|
||||
<th>Download link</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>YAML file containing all your actions, contexts, projects, tags and notes</td>
|
||||
<td><%= link_to "YAML file", :controller => 'data', :action => 'yaml_export' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CSV file containing all of your actions, with named contexts and projects</td>
|
||||
<td><%= link_to "CSV file (actions, contexts and projects)", :controller => 'data', :action => 'csv_actions' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CSV file containing all your notes</td>
|
||||
<td><%= link_to "CSV file (notes only)", :controller => 'data', :action => 'csv_notes' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>XML file containing all your actions, contexts, projects, tags and notes</td>
|
||||
<td><%= link_to "XML file (actions only)", :controller => 'data', :action => 'xml_export' %></td>
|
||||
</tr>
|
||||
</table>
|
||||
</p>
|
||||
|
||||
</div><!-- End of feeds -->
|
||||
<script type="text/javascript">
|
||||
window.onload=function(){
|
||||
Nifty("div#feedlegend","normal");
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,19 +1,23 @@
|
|||
<div id="display_box">
|
||||
<div id="feeds">
|
||||
<div id="feedlegend">
|
||||
<p>Paste the contents of the YAML file you exported into the text box below:</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<% form_for :import, @import, :url => {:controller => 'data', :action => 'yaml_import'} do |f| %>
|
||||
<%= f.text_area :yaml %><br />
|
||||
<input type="submit" value="Import data">
|
||||
<% end %>
|
||||
</p>
|
||||
|
||||
</div><!-- End of feeds -->
|
||||
<div id="feeds">
|
||||
<div id="feedlegend">
|
||||
<p><b>Beware</b>: all your current data will be destroyed before importing
|
||||
the YAML file, so if you have access to the database, we strongly recommend
|
||||
backing up the database right now in case that anything goes wrong.
|
||||
</p>
|
||||
<p>Paste the contents of the YAML file you exported into the text box below:</p>
|
||||
</div>
|
||||
<p>
|
||||
<% form_for :import, @import, :url => {:controller => 'data', :action => 'yaml_import'} do |f| %>
|
||||
<%= f.text_area :yaml %><br />
|
||||
<input type="submit" value="Import data">
|
||||
<% end %>
|
||||
</p>
|
||||
</div><!-- End of feeds -->
|
||||
</div><!-- End of display_box -->
|
||||
|
||||
<div id="input_box">
|
||||
|
||||
</div><!-- End of input box -->
|
||||
<script type="text/javascript">
|
||||
window.onload=function(){
|
||||
Nifty("div#feedlegend","normal");
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1 +1,7 @@
|
|||
<p>Import was successful</p>
|
||||
<% if !(@errmessage == '') %>
|
||||
<p>There were these errors:
|
||||
<pre><%= @errmessage %></pre>
|
||||
</p>
|
||||
<% else %>
|
||||
<p>Import was successful.</p>
|
||||
<% end %>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,18 @@
|
|||
<p>Do you have one of your own to add? <a href="http://www.rousette.org.uk/projects/forums/viewforum/10/" title="Tracks | Tips and Tricks">Tell us about it in our Tips and Tricks forum
|
||||
</a> and we may include it on this page in a future versions of Tracks.</p>
|
||||
|
||||
<a name="message_gateway"> </a>
|
||||
<h2>Integrated email/SMS receiver</h2>
|
||||
<p>
|
||||
If Tracks is running on the same server as your mail server, you can use the integrated mail handler built into tracks. Steps to set it up:
|
||||
<ul>
|
||||
<li>Go to <%= link_to "Preferences", preferences_url %> and set your "From email" and "default email context" for todos sent in via email (which could come from an SMS message)</li>
|
||||
<li>In sendmail/qmail/postfix/whatever, set up an email address alias to pipe messages to <pre style="font-size:1.5em">/PATH/TO/RUBY/ruby /PATH/TO/TRACKS/script/runner -e production 'MessageGateway.receive(STDIN.read)'</pre></li>
|
||||
<li>Send an email to your newly configured address!</li>
|
||||
</ul>
|
||||
You can also use the Rich Todo API to send in tasks like "do laundry @ Home" or "Call Bill > project X". The subject of the message will fill description, context, and project, while the body will populate the tasks's note.
|
||||
</p>
|
||||
|
||||
<a name="applescript1-section"> </a>
|
||||
<h2>Add an Action with Applescript</h2>
|
||||
<p>This is a simple script that pops up a dialog box asking for a description, and then sends that to Tracks with a hard-coded context.</p>
|
||||
|
|
|
|||
|
|
@ -16,9 +16,6 @@
|
|||
<script type="text/javascript">
|
||||
window.onload=function(){
|
||||
Nifty("div#todo_new_action_container","normal");
|
||||
Nifty("div#project_new_project_container","normal");
|
||||
Nifty("div#context_new_container","normal");
|
||||
Nifty("div#recurring_new_container","normal");
|
||||
if ($('flash').visible()) { new Effect.Fade("flash",{duration:5.0}); }
|
||||
}
|
||||
</script>
|
||||
|
|
@ -57,6 +54,7 @@ window.onload=function(){
|
|||
<% if current_user.is_admin? -%>
|
||||
<li><%= navigation_link("Admin", users_path, {:accesskey => "a", :title => "Add or delete users"} ) %></li>
|
||||
<% end -%>
|
||||
<li><%= navigation_link(image_tag("x-office-calendar.png", :size => "16X16", :border => 0), calendar_path, :title => "Calendar of due actions" ) %></li>
|
||||
<li><%= navigation_link(image_tag("recurring_menu16x16.png", :size => "16X16", :border => 0), {:controller => "recurring_todos", :action => "index"}, :title => "Manage recurring actions" ) %></li>
|
||||
<li><%= navigation_link(image_tag("feed-icon.png", :size => "16X16", :border => 0), {:controller => "feedlist", :action => "index"}, :title => "See a list of available feeds" ) %></li>
|
||||
<li><%= navigation_link(image_tag("menustar.gif", :size => "16X16", :border => 0), tag_path("starred"), :title => "See your starred actions" ) %></li>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<div id="<%= dom_id(note, 'container') %>">
|
||||
<h2><%= link_to("Note #{note.id}", note_path(note), :title => "Show note #{note.id}" ) %></h2>
|
||||
<div id="<%= dom_id(note) %>">
|
||||
<%= sanitize(textilize(note.body.gsub(/((https?:\/\/[^ \n\t]*))/, '"\1":\2'))) %>
|
||||
<%= sanitize(markdown(auto_link(note.body))) %>
|
||||
|
||||
<div class="note_footer">
|
||||
<%= link_to_remote(
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@
|
|||
<li><strong>refresh:</strong> automatic refresh interval for each of the pages (in minutes)</li>
|
||||
<li><strong>verbose action descriptor:</strong> when true, show project/context name in action listing; when false show [P]/[C] with tool tips</li>
|
||||
<li><strong>mobile todos per page:</strong> the maximum number of actions to show on a single page in the mobile view</li>
|
||||
<li><strong>From email:</string> the email address you use for sending todos as email or SMS messages to your Tracks account</li>
|
||||
<li><string>Default email context:</string> the context to which tasks sent in via email or SMS should be added</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
|
@ -67,6 +69,8 @@
|
|||
<%= row_with_text_field('refresh') %>
|
||||
<%= row_with_select_field("verbose_action_descriptors") %>
|
||||
<%= row_with_text_field("mobile_todos_per_page") %>
|
||||
<%= row_with_text_field("sms_email") %>
|
||||
<%= table_row("sms_context", false) { select('prefs', 'sms_context_id', current_user.contexts.map{|c| [c.name, c.id]}) } %>
|
||||
|
||||
<tr><td><%= submit_tag "Update" %></td>
|
||||
<td><%= link_to "Cancel", :action => 'index' %></td>
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@
|
|||
<li>Refresh interval (in minutes): <span class="highlight"><%= prefs.refresh %></span></li>
|
||||
<li>Verbose action descriptors: <span class="highlight"><%= prefs.verbose_action_descriptors %></span></li>
|
||||
<li>Actions per page (Mobile View): <span class="highlight"><%= prefs.mobile_todos_per_page %></span></li>
|
||||
<li>From email: <span class="highlight"><%= prefs.sms_email %></span></li>
|
||||
<li>Default email context: <span class="highlight"><%= prefs.sms_context.nil? ? "None" : prefs.sms_context.name %></span></li>
|
||||
</ul>
|
||||
<div class="actions">
|
||||
<%= link_to "Edit preferences »", { :controller => 'preferences', :action => 'edit'}, :class => 'edit_link' %>
|
||||
|
|
|
|||
|
|
@ -61,4 +61,10 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.onload=function(){
|
||||
Nifty("div#project_new_project_container","normal");
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -101,22 +101,22 @@
|
|||
<div id="recurring_monthly" style="display:none">
|
||||
<label>Settings for monthly recurring actions</label><br/>
|
||||
<%= radio_button_tag('recurring_todo[monthly_selector]', 'monthly_every_x_day', true)%> Day <%=
|
||||
text_field_tag('recurring_todo[monthly_every_x_day]', Time.now.mday, {"size" => 3, "tabindex" => 9}) %> on every <%=
|
||||
text_field_tag('recurring_todo[monthly_every_x_day]', Time.zone.now.mday, {"size" => 3, "tabindex" => 9}) %> on every <%=
|
||||
text_field_tag('recurring_todo[monthly_every_x_month]', 1, {"size" => 3, "tabindex" => 10}) %> month<br/>
|
||||
<%= radio_button_tag('recurring_todo[monthly_selector]', 'monthly_every_xth_day')%> The <%=
|
||||
select_tag('recurring_todo[monthly_every_xth_day]', options_for_select(@xth_day), {}) %> <%=
|
||||
select_tag('recurring_todo[monthly_day_of_week]' , options_for_select(@days_of_week, Time.now.wday), {}) %> of every <%=
|
||||
select_tag('recurring_todo[monthly_day_of_week]' , options_for_select(@days_of_week, Time.zone.now.wday), {}) %> of every <%=
|
||||
text_field_tag('recurring_todo[monthly_every_x_month2]', 1, {"size" => 3, "tabindex" => 11}) %> month<br/>
|
||||
</div>
|
||||
<div id="recurring_yearly" style="display:none">
|
||||
<label>Settings for yearly recurring actions</label><br/>
|
||||
<%= radio_button_tag('recurring_todo[yearly_selector]', 'yearly_every_x_day', true)%> Every <%=
|
||||
select_tag('recurring_todo[yearly_month_of_year]', options_for_select(@months_of_year, Time.now.month), {}) %> <%=
|
||||
text_field_tag('recurring_todo[yearly_every_x_day]', Time.now.day, "size" => 3, "tabindex" => 9) %><br/>
|
||||
select_tag('recurring_todo[yearly_month_of_year]', options_for_select(@months_of_year, Time.zone.now.month), {}) %> <%=
|
||||
text_field_tag('recurring_todo[yearly_every_x_day]', Time.zone.now.day, "size" => 3, "tabindex" => 9) %><br/>
|
||||
<%= radio_button_tag('recurring_todo[yearly_selector]', 'yearly_every_xth_day')%> The <%=
|
||||
select_tag('recurring_todo[yearly_every_xth_day]', options_for_select(@xth_day), {}) %> <%=
|
||||
select_tag('recurring_todo[yearly_day_of_week]', options_for_select(@days_of_week, Time.now.wday), {}) %> of <%=
|
||||
select_tag('recurring_todo[yearly_month_of_year2]', options_for_select(@months_of_year, Time.now.month), {}) %><br/>
|
||||
select_tag('recurring_todo[yearly_day_of_week]', options_for_select(@days_of_week, Time.zone.now.wday), {}) %> of <%=
|
||||
select_tag('recurring_todo[yearly_month_of_year2]', options_for_select(@months_of_year, Time.zone.now.month), {}) %><br/>
|
||||
</div>
|
||||
<div id="recurring_target">
|
||||
<label>Set recurrence on</label><br/>
|
||||
|
|
|
|||
|
|
@ -40,3 +40,9 @@
|
|||
apply_behaviour "#recurring_edit_period:click",
|
||||
"TracksForm.hide_all_edit_recurring(); $('recurring_edit_'+TracksForm.get_edit_period()).show();"
|
||||
-%>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.onload=function(){
|
||||
Nifty("div#recurring_new_container","normal");
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
<%= remote_star_icon %>
|
||||
<%= remote_toggle_checkbox unless source_view_is :deferred %>
|
||||
<div class="description<%= staleness_class( todo ) %>">
|
||||
<% unless @todo.completed? %><span class="defer-container"><%= defer_link(1) %> <%= defer_link(7) %></span><% end %>
|
||||
<%= date_span -%>
|
||||
<span class="todo.descr"><%= h sanitize(todo.description) %></span>
|
||||
<%= link_to(image_tag("recurring16x16.png"), {:controller => "recurring_todos", :action => "index"}, :class => "recurring_icon") if @todo.from_recurring_todo? %>
|
||||
|
|
|
|||
|
|
@ -3,5 +3,5 @@
|
|||
element.next('.todo_notes').toggle
|
||||
end -%>
|
||||
<div class="todo_notes" id="<%= dom_id(item, 'notes') %>" style="display:none">
|
||||
<%= markdown( item.notes.gsub(/((https?:\/\/[^ \n\t]*))/, '"\1":\2') ) %>
|
||||
<%= sanitize(markdown( auto_link(item.notes)) ) %>
|
||||
</div>
|
||||
68
app/views/todos/calendar.html.erb
Normal file
68
app/views/todos/calendar.html.erb
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<div id="display_box">
|
||||
|
||||
<div class="container">
|
||||
<h2>Due today</h2>
|
||||
<div id="empty_due_today" <%= "style=\"display:none\"" unless @due_today.empty? %>>
|
||||
No actions due today
|
||||
</div>
|
||||
<div id="due_today">
|
||||
<%= render :partial => "todos/todo", :collection => @due_today %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<h2>Due in rest of this week</h2>
|
||||
<div id="empty_due_this_week" <%= "style=\"display:none\"" unless @due_this_week.empty? %>>
|
||||
No actions due in rest of this week
|
||||
</div>
|
||||
<div id="due_this_week">
|
||||
<%= render :partial => "todos/todo", :collection => @due_this_week %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<h2>Due next week</h2>
|
||||
<div id="empty_due_next_week" <%= "style=\"display:none\"" unless @due_next_week.empty? %>>
|
||||
No actions due in next week
|
||||
</div>
|
||||
<div id="due_next_week">
|
||||
<%= render :partial => "todos/todo", :collection => @due_next_week %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<h2>Due in rest of this month</h2>
|
||||
<div id="empty_due_this_month" <%= "style=\"display:none\"" unless @due_this_month.empty? %>>
|
||||
No actions due in rest of this month
|
||||
</div>
|
||||
<div id="due_this_month">
|
||||
<%= render :partial => "todos/todo", :collection => @due_this_month %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<h2>Due next month and later</h2>
|
||||
<div id="empty_due_after_this_month" <%= "style=\"display:none\"" unless @due_after_this_month.empty? %>>
|
||||
No actions due after this month
|
||||
</div>
|
||||
<div id="due_after_this_month">
|
||||
<%= render :partial => "todos/todo", :collection => @due_after_this_month %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- End of display_box -->
|
||||
<div class="input_box" id="input_box">
|
||||
<!--
|
||||
<input class="hide_tickler" id="hide_tickler" type="checkbox" tabindex="5" name="hide_tickler" checked="true"/>
|
||||
<label for="hide_tickler"> Show actions in tickler</label>
|
||||
<br/><br/>
|
||||
-->
|
||||
<p><%= link_to('<span class="feed">iCal</span>', {:format => 'ics', :token => current_user.token}, :title => "iCal feed" ) %>
|
||||
- Get this calendar in iCal format</p>
|
||||
</div>
|
||||
|
||||
<%
|
||||
apply_behavior 'input.hide_tickler:click', :prevent_default => true do |page|
|
||||
page << "alert('hiding action in tickler from calendar is not yet implemented');"
|
||||
end
|
||||
%>
|
||||
31
app/views/todos/calendar.ics.erb
Normal file
31
app/views/todos/calendar.ics.erb
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
BEGIN:VCALENDAR
|
||||
PRODID:-//TRACKS//<%= TRACKS_VERSION %>//EN
|
||||
VERSION:2.0
|
||||
CALSCALE:GREGORIAN
|
||||
METHOD:PUBLISH
|
||||
X-WR-CALNAME:Tracks
|
||||
<% for todo in @due_all
|
||||
due_date = todo.due
|
||||
overdue_text = ""
|
||||
if due_date.at_midnight < Time.zone.now.at_midnight
|
||||
due_date = Time.zone.now
|
||||
overdue_text = "Overdue: "
|
||||
end
|
||||
%>BEGIN:VEVENT
|
||||
DTSTART;VALUE=DATE:<%= due_date.strftime("%Y%m%d") %>
|
||||
DTEND;VALUE=DATE:<%= (due_date+1.day).strftime("%Y%m%d") %>
|
||||
DTSTAMP:<%= due_date.strftime("%Y%m%dT%H%M%SZ") %>
|
||||
UID:<%= todo_url(todo) %>
|
||||
CLASS:PUBLIC
|
||||
CATEGORIES:Tracks
|
||||
CREATED:<%= todo.created_at.strftime("%Y%m%dT%H%M%SZ") %>
|
||||
DESCRIPTION:<%= format_ical_notes(todo.notes) %>
|
||||
LAST-MODIFIED:<%= due_date.strftime("%Y%m%dT%H%M%SZ") %>
|
||||
LOCATION:
|
||||
SEQUENCE:0
|
||||
STATUS:CONFIRMED
|
||||
SUMMARY:<%= overdue_text + todo.description %>
|
||||
TRANSP:TRANSPARENT
|
||||
END:VEVENT
|
||||
<% end
|
||||
%>END:VCALENDAR
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
if @saved
|
||||
page[@todo].remove
|
||||
page.show "empty_"+@original_item_due_id if @old_due_empty
|
||||
page['badge_count'].replace_html @down_count
|
||||
|
||||
# remove context if empty
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
if @saved
|
||||
page[@todo].remove
|
||||
page.show "empty_"+@original_item_due_id if @old_due_empty
|
||||
if @todo.completed?
|
||||
|
||||
# completed todos move from their context to the completed container
|
||||
|
|
@ -26,7 +27,7 @@ if @saved
|
|||
page.notify :notice, "There is no next action after the recurring action you just finished. The recurrence is completed", 6.0 if @new_recurring_todo.nil?
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
else
|
||||
# todo is activated from completed container
|
||||
page.call "todoItems.ensureVisibleWithEffectAppear", item_container_id(@todo)
|
||||
|
|
|
|||
|
|
@ -16,10 +16,18 @@ if @saved
|
|||
|
||||
if (@remaining_in_context == 0)
|
||||
# remove context container from page if empty
|
||||
source_view do |from|
|
||||
from.todo { page.visual_effect :fade, "c#{@original_item_context_id}", :duration => 0.4 }
|
||||
from.tag { page.visual_effect :fade, "c#{@original_item_context_id}", :duration => 0.4 }
|
||||
from.context { page.show "c#{@original_item_context_id}empty-nd" }
|
||||
if @context_changed
|
||||
source_view do |from|
|
||||
from.todo { page.visual_effect :fade, "c#{@original_item_context_id}", :duration => 0.4 }
|
||||
from.tag { page.visual_effect :fade, "c#{@original_item_context_id}", :duration => 0.4 }
|
||||
from.context { page.show "c#{@original_item_context_id}empty-nd" }
|
||||
end
|
||||
else
|
||||
source_view do |from|
|
||||
from.todo { page.visual_effect :fade, item_container_id(@todo), :duration => 0.4 }
|
||||
from.tag { page.visual_effect :fade, item_container_id(@todo), :duration => 0.4 }
|
||||
from.context { page.show "c#{@original_item_context_id}empty-nd" }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -93,6 +101,23 @@ if @saved
|
|||
elsif source_view_is :stats
|
||||
page.replace dom_id(@todo), :partial => 'todos/todo', :locals => { :parent_container_type => parent_container_type }
|
||||
page.visual_effect :highlight, dom_id(@todo), :duration => 3
|
||||
elsif source_view_is :calendar
|
||||
if @due_date_changed
|
||||
page[@todo].remove
|
||||
page.show "empty_"+@original_item_due_id if @old_due_empty
|
||||
page.hide "empty_"+@new_due_id
|
||||
page.insert_html :bottom, @new_due_id, :partial => 'todos/todo'
|
||||
page.visual_effect :highlight, dom_id(@todo), :duration => 3
|
||||
else
|
||||
if @todo.due.nil?
|
||||
# due date removed
|
||||
page[@todo].remove
|
||||
page.show "empty_"+@original_item_due_id if @old_due_empty
|
||||
else
|
||||
page.replace dom_id(@todo), :partial => 'todos/todo', :locals => { :parent_container_type => parent_container_type }
|
||||
page.visual_effect :highlight, dom_id(@todo), :duration => 3
|
||||
end
|
||||
end
|
||||
else
|
||||
logger.error "unexpected source_view '#{params[:_source_view]}'"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -43,9 +43,12 @@ ActionController::Routing::Routes.draw do |map|
|
|||
# so /todos/tag/version1.5.xml will result in :name => 'version1.5.xml'
|
||||
# UPDATE: added support for mobile view. All tags ending on .m will be
|
||||
# routed to mobile view of tags.
|
||||
todos.tag 'todos/tag/:name', :action => "tag", :format => 'm', :name => /.*\.m/
|
||||
todos.tag 'todos/tag/:name.m', :action => "tag", :format => 'm'
|
||||
todos.tag 'todos/tag/:name', :action => "tag", :name => /.*/
|
||||
|
||||
todos.calendar 'calendar.ics', :action => "calendar", :format => 'ics'
|
||||
todos.calendar 'calendar', :action => "calendar"
|
||||
|
||||
todos.mobile 'mobile', :action => "index", :format => 'm'
|
||||
todos.mobile_abbrev 'm', :action => "index", :format => 'm'
|
||||
todos.mobile_abbrev_new 'm/new', :action => "new", :format => 'm'
|
||||
|
|
@ -55,6 +58,10 @@ ActionController::Routing::Routes.draw do |map|
|
|||
map.feeds 'feeds', :controller => 'feedlist', :action => 'index'
|
||||
map.feeds 'feeds.m', :controller => 'feedlist', :action => 'index', :format => 'm'
|
||||
|
||||
if Rails.env == 'test'
|
||||
map.connect '/selenium_helper/login', :controller => 'selenium_helper', :action => 'login'
|
||||
end
|
||||
|
||||
map.preferences 'preferences', :controller => 'preferences', :action => 'index'
|
||||
map.integrations 'integrations', :controller => 'integrations', :action => 'index'
|
||||
|
||||
|
|
|
|||
11
db/migrate/041_add_sms_to_preference.rb
Normal file
11
db/migrate/041_add_sms_to_preference.rb
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
class AddSmsToPreference < ActiveRecord::Migration
|
||||
def self.up
|
||||
add_column :preferences, :sms_email, :string
|
||||
add_column :preferences, :sms_context_id, :integer
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_column :preferences, :sms_context_id
|
||||
remove_column :preferences, :sms_email
|
||||
end
|
||||
end
|
||||
27
db/migrate/042_change_dates_to_datetimes.rb
Normal file
27
db/migrate/042_change_dates_to_datetimes.rb
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
class ChangeDatesToDatetimes < ActiveRecord::Migration
|
||||
def self.up
|
||||
change_column :todos, :show_from, :datetime
|
||||
change_column :todos, :due, :datetime
|
||||
change_column :recurring_todos, :start_from, :datetime
|
||||
change_column :recurring_todos, :end_date, :datetime
|
||||
|
||||
User.all(:include => [:todos, :recurring_todos]).each do |user|
|
||||
user.todos.each do |todo|
|
||||
todo.update_attribute(:show_from, user.at_midnight(todo.show_from)) unless todo.show_from.nil?
|
||||
todo.update_attribute(:due, user.at_midnight(todo.due)) unless todo.due.nil?
|
||||
end
|
||||
|
||||
user.recurring_todos.each do |todo|
|
||||
todo.update_attribute(:start_from, user.at_midnight(todo.start_from)) unless todo.start_from.nil?
|
||||
todo.update_attribute(:end_date, user.at_midnight(todo.end_date)) unless todo.end_date.nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
change_column :todos, :show_from, :date
|
||||
change_column :todos, :due, :date
|
||||
change_column :recurring_todos, :start_from, :date
|
||||
change_column :recurring_todos, :end_date, :date
|
||||
end
|
||||
end
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
* Homepage: http://www.rousette.org.uk/projects/
|
||||
* Author: bsag (http://www.rousette.org.uk/)
|
||||
* Contributors: Nicholas Lee, Lolindrath, Jim Ray, Arnaud Limbourg, Timothy Martens, Luke Melia, John Leonard, Jim Strupp, Eric Lesh, Damien Cirotteau, Janet Riley, Reinier Balt, Jacqui Maher, James Kebinger, Jeffrey Gipson
|
||||
* Contributors: Nicholas Lee, Lolindrath, Jim Ray, Arnaud Limbourg, Timothy Martens, Luke Melia, John Leonard, Jim Strupp, Eric Lesh, Damien Cirotteau, Janet Riley, Reinier Balt, Jacqui Maher, James Kebinger, Jeffrey Gipson, Eric Allen
|
||||
* Version: 1.6
|
||||
* Copyright: (cc) 2004-2008 rousette.org.uk
|
||||
* License: GNU GPL
|
||||
|
|
@ -13,6 +13,20 @@ Trac (for bug reports and feature requests): http://dev.rousette.org.uk/report/6
|
|||
|
||||
Wiki (deprecated - please use Trac): http://www.rousette.org.uk/projects/wiki/
|
||||
|
||||
== Version 1.7dev
|
||||
|
||||
New features:
|
||||
1. Recurring todos
|
||||
2. Cleanup of feed page and add feed for starred actions
|
||||
3. Initial importer of yaml files (still very EXPERIMENTAL)
|
||||
4. New interface to import an email / sms messages into Tracks (needs an email server on the same server as Tracks)
|
||||
5. New buttons to quickly defer an action 1 or 7 days
|
||||
6. Calendar view to review due actions, includes iCal feed to use in your calendar app (tested with Google Calendar, Evolution, Outlook 2007)
|
||||
|
||||
Under the hood:
|
||||
1. Move selenium tests to RSpec
|
||||
2. Bugfixes
|
||||
|
||||
== Version 1.6
|
||||
1. upgrade to rails 2.0.2
|
||||
2. new mobile interface (with some iPhone compatibility fixes)
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ RewriteEngine On
|
|||
RewriteRule ^$ index.html [QSA]
|
||||
RewriteRule ^([^.]+)$ $1.html [QSA]
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^(.*)$ dispatch.cgi [QSA,L]
|
||||
RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
|
||||
|
||||
# In case Rails experiences terminal errors
|
||||
# Instead of displaying this message you can supply a file here which will be rendered instead
|
||||
|
|
|
|||
BIN
public/images/defer_1.png
Normal file
BIN
public/images/defer_1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3 KiB |
BIN
public/images/defer_7.png
Normal file
BIN
public/images/defer_7.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
BIN
public/images/x-office-calendar.png
Normal file
BIN
public/images/x-office-calendar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 515 B |
|
|
@ -1200,4 +1200,10 @@ body.integrations textarea {
|
|||
padding:3px;
|
||||
width:80%;
|
||||
background-color:#ddd;
|
||||
}
|
||||
}
|
||||
.defer-container {
|
||||
float:right;
|
||||
}
|
||||
.defer-container a:hover {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -220,6 +220,8 @@ body.integrations h2 {margin-top:40px; padding-top:20px; margin-bottom:10px; bor
|
|||
body.integrations p, body.integrations li {font-size:1.0em}
|
||||
body.integrations li {list-style-type: disc; list-style-position: inside; margin-left:30px}
|
||||
body.integrations textarea {margin:10px; padding:3px; width:80%; background-color:#ddd}
|
||||
.defer-container {float:right}
|
||||
.defer-container a:hover {background-color: inherit}
|
||||
div.calendar {position: relative}
|
||||
.calendar, .calendar table {border: 1px solid #556; font-size: 11px; color: #000; cursor: default; background: #eef; z-index: 110; font-family: tahoma,verdana,sans-serif}
|
||||
.calendar .button {text-align: center; padding: 2px}
|
||||
|
|
@ -10,7 +10,7 @@ describe Todo do
|
|||
|
||||
def create_todo(attributes={})
|
||||
todo = Todo.new(valid_attributes(attributes))
|
||||
todo.stub!(:user).and_return(mock_model(User, :date => Time.now))
|
||||
todo.stub!(:user).and_return(mock_model(User, :date => Time.zone.now))
|
||||
todo.save!
|
||||
todo
|
||||
end
|
||||
|
|
@ -32,7 +32,7 @@ describe Todo do
|
|||
|
||||
it 'ensures that show_from is a date in the future' do
|
||||
todo = Todo.new(valid_attributes)
|
||||
todo.stub!(:user).and_return(mock_model(User, :date => Time.now))
|
||||
todo.stub!(:user).and_return(mock_model(User, :date => Time.zone.now))
|
||||
todo.show_from = 3.days.ago
|
||||
todo.should have(1).error_on(:show_from)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ describe User do
|
|||
it 'has many active contexts' do
|
||||
User.should have_many(:active_contexts).
|
||||
with_order('position ASC').
|
||||
with_conditions('hide = ?', 'true').
|
||||
with_conditions('hide = ?', false).
|
||||
with_class_name('Context')
|
||||
end
|
||||
|
||||
|
|
@ -178,4 +178,20 @@ describe User do
|
|||
@user.remember_token_expires_at.should be_between(before, after)
|
||||
end
|
||||
end
|
||||
|
||||
it "should not activate todos that are showing when UTC is tomorrow" do
|
||||
context = Context.create(:name => 'a context')
|
||||
user = User.create(:login => 'user7', :password => 'foobar', :password_confirmation => 'foobar')
|
||||
user.save!
|
||||
user.create_preference
|
||||
user.preference.update_attribute('time_zone', 'Pacific Time (US & Canada)')
|
||||
# Time.zone = 'Pacific Time (US & Canada)'
|
||||
Time.stub!(:now).and_return(Time.new.end_of_day - 20.minutes)
|
||||
todo = user.todos.build(:description => 'test task', :context => context, :show_from => user.date + 1.days)
|
||||
todo.save!
|
||||
|
||||
user.deferred_todos.find_and_activate_ready
|
||||
user = User.find(user.id)
|
||||
user.deferred_todos.should include(todo)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
36
spec/views/notes/_notes.rhtml_spec.rb
Normal file
36
spec/views/notes/_notes.rhtml_spec.rb
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
require File.dirname(__FILE__) + '/../../spec_helper'
|
||||
|
||||
describe "/notes/_notes.rhtml" do
|
||||
before :each do
|
||||
@project = mock_model(Project, :name => "a project")
|
||||
@note = mock_model(Note, :body => "this is a note", :project => @project,
|
||||
:created_at => Time.now, :updated_at? => false)
|
||||
@controller.template.stub!(:apply_behavior)
|
||||
@controller.template.stub!(:format_date)
|
||||
@controller.template.stub!(:render)
|
||||
@controller.template.stub!(:form_remote_tag)
|
||||
end
|
||||
|
||||
it "should render" do
|
||||
render :partial => "/notes/notes", :locals => {:notes => @note}
|
||||
response.should have_tag("div.note_footer")
|
||||
end
|
||||
|
||||
it "should auto-link URLs" do
|
||||
@note.stub!(:body).and_return("http://www.google.com/")
|
||||
render :partial => "/notes/notes", :locals => {:notes => @note}
|
||||
response.should have_tag("a[href=\"http://www.google.com/\"]")
|
||||
end
|
||||
|
||||
it "should auto-link embedded URLs" do
|
||||
@note.stub!(:body).and_return("this is cool: http://www.google.com/")
|
||||
render :partial => "/notes/notes", :locals => {:notes => @note}
|
||||
response.should have_tag("a[href=\"http://www.google.com/\"]")
|
||||
end
|
||||
|
||||
it "should parse Textile links correctly" do
|
||||
@note.stub!(:body).and_return("\"link\":http://www.google.com/")
|
||||
render :partial => "/notes/notes", :locals => {:notes => @note}
|
||||
response.should have_tag("a[href=\"http://www.google.com/\"]")
|
||||
end
|
||||
end
|
||||
33
spec/views/todos/_toggle_notes.rhtml_spec.rb
Normal file
33
spec/views/todos/_toggle_notes.rhtml_spec.rb
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
require File.dirname(__FILE__) + '/../../spec_helper'
|
||||
|
||||
describe "/todos/_toggle_notes.rhtml" do
|
||||
# include ControllerHelper
|
||||
|
||||
before :each do
|
||||
@item = mock_model(Todo, :notes => "this is a note")
|
||||
@controller.template.stub!(:apply_behavior)
|
||||
end
|
||||
|
||||
it "should render" do
|
||||
render :partial => "/todos/toggle_notes", :locals => {:item => @item}
|
||||
response.should have_tag("div.todo_notes")
|
||||
end
|
||||
|
||||
it "should auto-link URLs" do
|
||||
@item.stub!(:notes).and_return("http://www.google.com/")
|
||||
render :partial => "/todos/toggle_notes", :locals => {:item => @item}
|
||||
response.should have_tag("a[href=\"http://www.google.com/\"]")
|
||||
end
|
||||
|
||||
it "should auto-link embedded URLs" do
|
||||
@item.stub!(:notes).and_return("this is cool: http://www.google.com/")
|
||||
render :partial => "/todos/toggle_notes", :locals => {:item => @item}
|
||||
response.should have_tag("a[href=\"http://www.google.com/\"]")
|
||||
end
|
||||
|
||||
it "should parse Textile URLs correctly" do
|
||||
@item.stub!(:notes).and_return("\"link\":http://www.google.com/")
|
||||
render :partial => "/todos/toggle_notes", :locals => {:item => @item}
|
||||
response.should have_tag("a[href=\"http://www.google.com/\"]")
|
||||
end
|
||||
end
|
||||
18
test/fixtures/contexts.yml
vendored
18
test/fixtures/contexts.yml
vendored
|
|
@ -113,3 +113,21 @@ someday_maybe:
|
|||
user_id: 1
|
||||
created_at: <%= today %>
|
||||
updated_at: <%= today %>
|
||||
|
||||
inbox:
|
||||
id: 13
|
||||
name: Inbox
|
||||
position: 1
|
||||
hide: false
|
||||
user_id: 4
|
||||
created_at: <%= today %>
|
||||
updated_at: <%= today %>
|
||||
|
||||
anothercontext:
|
||||
id: 14
|
||||
name: anothercontext
|
||||
position: 2
|
||||
hide: false
|
||||
user_id: 4
|
||||
created_at: <%= today %>
|
||||
updated_at: <%= today %>
|
||||
|
|
|
|||
20
test/fixtures/preferences.yml
vendored
20
test/fixtures/preferences.yml
vendored
|
|
@ -34,3 +34,23 @@ other_user_prefs:
|
|||
time_zone: "London"
|
||||
verbose_action_descriptors: false
|
||||
show_project_on_todo_done: true
|
||||
|
||||
sms_user_prefs:
|
||||
id: 3
|
||||
user_id: 4
|
||||
staleness_starts: 7
|
||||
date_format: "%d/%m/%Y"
|
||||
title_date_format: "%A, %d %B %Y"
|
||||
show_number_completed: 5
|
||||
show_completed_projects_in_sidebar: true
|
||||
show_hidden_contexts_in_sidebar: true
|
||||
show_hidden_projects_in_sidebar: true
|
||||
admin_email: butshesagirl@rousette.org.uk
|
||||
week_starts: 1
|
||||
due_style: 0
|
||||
refresh: 0
|
||||
time_zone: "London"
|
||||
verbose_action_descriptors: false
|
||||
show_project_on_todo_done: true
|
||||
sms_email: 5555555555@tmomail.net
|
||||
sms_context_id: 13
|
||||
374
test/fixtures/sample_mms.txt
vendored
Normal file
374
test/fixtures/sample_mms.txt
vendored
Normal file
|
|
@ -0,0 +1,374 @@
|
|||
Return-Path: <15555555555/TYPE=PLMN@tmomail.net>
|
||||
Date: Fri, 6 Jun 2008 21:38:26 -0400
|
||||
From: 15555555555@tmomail.net
|
||||
To: gtd@tracks.com
|
||||
Message-ID: <3645873.13759311212802713215.JavaMail.mms@rlyatl28>
|
||||
Subject: This is the subject
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/related; type="text/html";
|
||||
boundary="----=_Part_1240237_22156211.1212802713213"
|
||||
X-UA: treo_600
|
||||
Importance: Normal
|
||||
X-Mms-Sender-Visibility: Show
|
||||
X-MMS-Message-Type: MM4_forward.REQ
|
||||
X-Priority: 3
|
||||
X-Proofpoint-Spam-Reason: safe
|
||||
|
||||
------=_Part_1240237_22156211.1212802713213
|
||||
Content-Type: text/html
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
Content-ID: <0000>
|
||||
Content-Location:mms.smil
|
||||
Content-Disposition: inline
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>T-Mobile</title>=20
|
||||
<!--
|
||||
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
|
||||
If you can read this text, but much of the message below se=
|
||||
ems unreadable, you might be using an e-mail program that does not work wit=
|
||||
h HTML.
|
||||
=20
|
||||
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D-->
|
||||
<style type=3D"text/css">
|
||||
<!--
|
||||
=20
|
||||
.footer {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 11px;
|
||||
color: #555555;
|
||||
text-decoration: none;
|
||||
}
|
||||
.normal {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 10px;
|
||||
color: #555555;
|
||||
text-decoration: none;
|
||||
}
|
||||
=20
|
||||
-->
|
||||
</style>
|
||||
</head>
|
||||
<body marginwidth=3D"0" marginheight=3D"0" leftmargin=3D"0" topmarg=
|
||||
in=3D"0" bgcolor=3D"#ffffff">
|
||||
<table border=3D"0" width=3D"600" cellspacing=3D"0" cellpad=
|
||||
ding=3D"0">
|
||||
<tr>
|
||||
<td width=3D"20" rowspan=3D"8"><img src=3D"=
|
||||
cid:tmobilespace.gif" width=3D"20" height=3D"20"></td>
|
||||
<td width=3D"600" colspan=3D"2"><img src=3D=
|
||||
"cid:tmobilespace.gif" width=3D"600" height=3D"20"></td>
|
||||
<td width=3D"20" rowspan=3D"8"><img src=3D"=
|
||||
cid:tmobilespace.gif" width=3D"20" height=3D"20"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width=3D"600" colspan=3D"2"><img src=3D=
|
||||
"cid:dottedline600.gif" width=3D"600"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width=3D"370">
|
||||
<!-- presentation starts here -->
|
||||
<table border=3D0><tr><tr><td colspan=3D1=
|
||||
align=3D"Left">This is the message body</td></tr></tr><TR><TD width=3D350 =
|
||||
colSpan=3D1><IMG height=3D30 src=3D"cid:tmobilespace.gif" width=3D350></TD=
|
||||
></TR><TR><TD width=3D350 colSpan=3D4><IMG src=3D"cid:dottedline350.gif" w=
|
||||
idth=3D350></TD></TR><TR><TR><TD width=3D350 colSpan=3D4><IMG height=3D30 s=
|
||||
rc=3D"cid:tmobilespace.gif" width=3D350></TD></TR></table> =20
|
||||
<!-- presentation ends here -->
|
||||
</td>
|
||||
<td width=3D"240" bgcolor=3D"#f2f2f2"><BR><=
|
||||
/td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width=3D"600" colspan=3D"2"><img src=3D=
|
||||
"cid:tmobilelogo.gif" width=3D"600" height=3D"105"></td>
|
||||
</tr>
|
||||
</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width=3D"600" colspan=3D"2"><img src=3D=
|
||||
"cid:tmobilespace.gif" width=3D"600" height=3D"40"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</body></html>
|
||||
|
||||
|
||||
------=_Part_1240237_22156211.1212802713213
|
||||
Content-Type: text/plain; charset=utf-8; name=text.txt
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Content-ID: <133>
|
||||
Content-Location: text.txt
|
||||
Content-Disposition: inline
|
||||
|
||||
This is the message body
|
||||
------=_Part_1240237_22156211.1212802713213
|
||||
Content-Type: image/gif; name=dottedline350.gif
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-Disposition: attachment; filename=dottedline350.gif
|
||||
Content-ID: <dottedline350.gif>
|
||||
|
||||
R0lGODlhXgEBAPcAAAAAAIAAAACAAICAAAAAgIAAgACAgICAgMDAwP8AAAD/AP//AAAA//8A/wD/
|
||||
/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMwAAZgAAmQAAzAAA/wAzAAAzMwAzZgAzmQAzzAAz/wBm
|
||||
AABmMwBmZgBmmQBmzABm/wCZAACZMwCZZgCZmQCZzACZ/wDMAADMMwDMZgDMmQDMzADM/wD/AAD/
|
||||
MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMzADMzMzMzZjMzmTMzzDMz/zNmADNmMzNm
|
||||
ZjNmmTNmzDNm/zOZADOZMzOZZjOZmTOZzDOZ/zPMADPMMzPMZjPMmTPMzDPM/zP/ADP/MzP/ZjP/
|
||||
mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YzAGYzM2YzZmYzmWYzzGYz/2ZmAGZmM2ZmZmZmmWZm
|
||||
zGZm/2aZAGaZM2aZZmaZmWaZzGaZ/2bMAGbMM2bMZmbMmWbMzGbM/2b/AGb/M2b/Zmb/mWb/zGb/
|
||||
/5kAAJkAM5kAZpkAmZkAzJkA/5kzAJkzM5kzZpkzmZkzzJkz/5lmAJlmM5lmZplmmZlmzJlm/5mZ
|
||||
AJmZM5mZZpmZmZmZzJmZ/5nMAJnMM5nMZpnMmZnMzJnM/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwA
|
||||
M8wAZswAmcwAzMwA/8wzAMwzM8wzZswzmcwzzMwz/8xmAMxmM8xmZsxmmcxmzMxm/8yZAMyZM8yZ
|
||||
ZsyZmcyZzMyZ/8zMAMzMM8zMZszMmczMzMzM/8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8A
|
||||
mf8AzP8A//8zAP8zM/8zZv8zmf8zzP8z//9mAP9mM/9mZv9mmf9mzP9m//+ZAP+ZM/+ZZv+Zmf+Z
|
||||
zP+Z///MAP/MM//MZv/Mmf/MzP/M////AP//M///Zv//mf//zP///yH5BAEAABAALAAAAABeAQEA
|
||||
AAg1AFP9+yeQ4MCCCA8qNMgwYcOFDiNCnPiwokSLFC9qzMgRo8eNHzuCHCmyZMiTJFGaTMlSYUAA
|
||||
Ow==
|
||||
------=_Part_1240237_22156211.1212802713213
|
||||
Content-Type: image/gif; name=dottedline600.gif
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-Disposition: attachment; filename=dottedline600.gif
|
||||
Content-ID: <dottedline600.gif>
|
||||
|
||||
R0lGODlhWAIBAPcAAAAAAIAAAACAAICAAAAAgIAAgACAgICAgMDAwP8AAAD/AP//AAAA//8A/wD/
|
||||
/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMwAAZgAAmQAAzAAA/wAzAAAzMwAzZgAzmQAzzAAz/wBm
|
||||
AABmMwBmZgBmmQBmzABm/wCZAACZMwCZZgCZmQCZzACZ/wDMAADMMwDMZgDMmQDMzADM/wD/AAD/
|
||||
MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMzADMzMzMzZjMzmTMzzDMz/zNmADNmMzNm
|
||||
ZjNmmTNmzDNm/zOZADOZMzOZZjOZmTOZzDOZ/zPMADPMMzPMZjPMmTPMzDPM/zP/ADP/MzP/ZjP/
|
||||
mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YzAGYzM2YzZmYzmWYzzGYz/2ZmAGZmM2ZmZmZmmWZm
|
||||
zGZm/2aZAGaZM2aZZmaZmWaZzGaZ/2bMAGbMM2bMZmbMmWbMzGbM/2b/AGb/M2b/Zmb/mWb/zGb/
|
||||
/5kAAJkAM5kAZpkAmZkAzJkA/5kzAJkzM5kzZpkzmZkzzJkz/5lmAJlmM5lmZplmmZlmzJlm/5mZ
|
||||
AJmZM5mZZpmZmZmZzJmZ/5nMAJnMM5nMZpnMmZnMzJnM/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwA
|
||||
M8wAZswAmcwAzMwA/8wzAMwzM8wzZswzmcwzzMwz/8xmAMxmM8xmZsxmmcxmzMxm/8yZAMyZM8yZ
|
||||
ZsyZmcyZzMyZ/8zMAMzMM8zMZszMmczMzMzM/8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8A
|
||||
mf8AzP8A//8zAP8zM/8zZv8zmf8zzP8z//9mAP9mM/9mZv9mmf9mzP9m//+ZAP+ZM/+ZZv+Zmf+Z
|
||||
zP+Z///MAP/MM//MZv/Mmf/MzP/M////AP//M///Zv//mf//zP///yH5BAEAABAALAAAAABYAgEA
|
||||
AAhFAFP9+yeQ4MCCCA8qNMgwYcOFDiNCnPiwokSLFC9qzMgRo8eNHzuCHCmyZMiTJFGaTMlypUuV
|
||||
MFvGfCmzJs2bM3Pa9BgQADs=
|
||||
------=_Part_1240237_22156211.1212802713213
|
||||
Content-Type: image/gif; name=tmobilelogo.gif
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-Disposition: attachment; filename=tmobilelogo.gif
|
||||
Content-ID: <tmobilelogo.gif>
|
||||
|
||||
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
|
||||
d3Pf9lg/d3Vf94INCAA7
|
||||
------=_Part_1240237_22156211.1212802713213
|
||||
Content-Type: image/gif; name=tmobilespace.gif
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-Disposition: attachment; filename=tmobilespace.gif
|
||||
Content-ID: <tmobilespace.gif>
|
||||
|
||||
R0lGODlhAQABAPcAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwMDcwKbK8AAAMzMAADMAMwAzMxYW
|
||||
FhwcHCIiIikpKVVVVU1NTUJCQjk5Of98gP9QUNYAk8zs/+/Wxufn1q2pkDP/AGYAAJkAAMwAAAAz
|
||||
ADMzAGYzAJkzAMwzAP8zAABmADNmAGZmAJlmAMxmAP9mAACZADOZAGaZAJmZAMyZAP+ZAADMADPM
|
||||
AGbMAJnMAMzMAP/MAGb/AJn/AMz/AAD/MzMA/2YAM5kAM8wAM/8AMwAz/zMzM2YzM5kzM8wzM/8z
|
||||
MwBmMzNmM2ZmM5lmM8xmM/9mMwCZMzOZM2aZM5mZM8yZM/+ZMwDMMzPMM2bMM5nMM8zMM//MMzP/
|
||||
M2b/M5n/M8z/M///MwAAZjMAZmYAZpkAZswAZv8AZgAzZjMzZmYzZpkzZswzZv8zZgBmZjNmZmZm
|
||||
ZplmZsxmZgCZZjOZZmaZZpmZZsyZZv+ZZgDMZjPMZpnMZszMZv/MZgD/ZjP/Zpn/Zsz/Zv8AzMwA
|
||||
/wCZmZkzmZkAmcwAmQAAmTMzmWYAmcwzmf8AmQBmmTNmmWYzmZlmmcxmmf8zmTOZmWaZmZmZmcyZ
|
||||
mf+ZmQDMmTPMmWbMZpnMmczMmf/MmQD/mTP/mWbMmZn/mcz/mf//mQAAzDMAmWYAzJkAzMwAzAAz
|
||||
mTMzzGYzzJkzzMwzzP8zzABmzDNmzGZmmZlmzMxmzP9mmQCZzDOZzGaZzJmZzMyZzP+ZzADMzDPM
|
||||
zGbMzJnMzMzMzP/MzAD/zDP/zGb/mZn/zMz/zP//zDMAzGYA/5kA/wAzzDMz/2Yz/5kz/8wz//8z
|
||||
/wBm/zNm/2ZmzJlm/8xm//9mzACZ/zOZ/2aZ/5mZ/8yZ//+Z/wDM/zPM/2bM/5nM/8zM///M/zP/
|
||||
/2b/zJn//8z///9mZmb/Zv//ZmZm//9m/2b//6UAIV9fX3d3d4aGhpaWlsvLy7KystfX193d3ePj
|
||||
4+rq6vHx8fj4+P/78KCgpICAgP8AAAD/AP//AAAA//8A/wD//////ywAAAAAAQABAAAIBAD/BQQA
|
||||
Ow==
|
||||
------=_Part_1240237_22156211.1212802713213--
|
||||
12
test/fixtures/sample_sms.txt
vendored
Normal file
12
test/fixtures/sample_sms.txt
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
Return-Path: <5555555555@tmomail.net>
|
||||
Date: Tue, 3 Jun 2008 23:11:26 -0400
|
||||
From: 5555555555@tmomail.net
|
||||
To: gtd@tracks.com
|
||||
Message-ID: <6100602.65827251212549086388.JavaMail.imb@mgwatl02.cns.mms.com>
|
||||
Subject:
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain;charset=utf-8
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Importance: Normal
|
||||
|
||||
message_content
|
||||
10
test/fixtures/users.yml
vendored
10
test/fixtures/users.yml
vendored
|
|
@ -28,3 +28,13 @@ ldap_user:
|
|||
first_name: John
|
||||
last_name: Deere
|
||||
auth_type: ldap
|
||||
|
||||
sms_user:
|
||||
id: 4
|
||||
login: sms_user
|
||||
crypted_password: <%= Digest::SHA1.hexdigest("#{Tracks::Config.salt}--sesame--") %>
|
||||
token: <%= Digest::SHA1.hexdigest("sms_userSun Feb 19 14:42:45 GMT 20060.408173979260027") %>
|
||||
is_admin: false
|
||||
first_name: SMS
|
||||
last_name: Tester
|
||||
auth_type: database
|
||||
|
|
@ -25,7 +25,7 @@ class ProjectsControllerTest < TodoContainerControllerTestBase
|
|||
assert_equal 1, assigns['deferred'].size
|
||||
|
||||
t = p.not_done_todos[0]
|
||||
t.show_from = 1.days.from_now.utc.to_date
|
||||
t.show_from = 1.days.from_now.utc
|
||||
t.save!
|
||||
|
||||
get :show, :id => p.to_param
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ class RecurringTodosControllerTest < ActionController::TestCase
|
|||
new_todo = Todo.find_by_recurring_todo_id 5
|
||||
|
||||
# due date should be the target_date
|
||||
assert_equal Time.utc(target_date.year, target_date.month, target_date.day), new_todo.due
|
||||
assert_equal users(:admin_user).at_midnight(Date.new(target_date.year, target_date.month, target_date.day)), new_todo.due
|
||||
|
||||
# show_from should be nil since now+4.days-10.days is in the past
|
||||
assert_equal nil, new_todo.show_from
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ require 'todos_controller'
|
|||
class TodosController; def rescue_action(e) raise e end; end
|
||||
|
||||
class TodosControllerTest < Test::Rails::TestCase
|
||||
fixtures :users, :preferences, :projects, :contexts, :todos, :tags, :taggings
|
||||
fixtures :users, :preferences, :projects, :contexts, :todos, :tags, :taggings, :recurring_todos
|
||||
|
||||
def setup
|
||||
@controller = TodosController.new
|
||||
|
|
@ -137,7 +137,7 @@ class TodosControllerTest < Test::Rails::TestCase
|
|||
assert_equal "Call Warren Buffet to find out how much he makes per day", t.description
|
||||
assert_equal "foo, bar", t.tag_list
|
||||
expected = Date.new(2006,11,30)
|
||||
actual = t.due
|
||||
actual = t.due.to_date
|
||||
assert_equal expected, actual, "Expected #{expected.to_s(:db)}, was #{actual.to_s(:db)}"
|
||||
end
|
||||
|
||||
|
|
@ -328,7 +328,7 @@ class TodosControllerTest < Test::Rails::TestCase
|
|||
assert t.active?
|
||||
assert_equal 'test notes', t.notes
|
||||
assert_nil t.show_from
|
||||
assert_equal Date.new(2007,1,2).to_s, t.due.to_s
|
||||
assert_equal Date.new(2007,1,2), t.due.to_date
|
||||
end
|
||||
|
||||
def test_mobile_create_action_redirects_to_mobile_home_page_when_successful
|
||||
|
|
@ -374,8 +374,26 @@ class TodosControllerTest < Test::Rails::TestCase
|
|||
assert todo_1.completed?
|
||||
|
||||
# check there is a new todo linked to the recurring pattern
|
||||
next_todo = Todo.find_by_recurring_todo_id(recurring_todo_1.id)
|
||||
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
|
||||
|
||||
# change recurrence pattern to weekly and set show_from 2 days befor due date
|
||||
# this forces the next todo to be put in the tickler
|
||||
recurring_todo_1.show_from_delta = 2
|
||||
recurring_todo_1.recurring_period = 'weekly'
|
||||
recurring_todo_1.every_day = 'smtwtfs'
|
||||
recurring_todo_1.save
|
||||
|
||||
# mark next_todo as complete by toggle_check
|
||||
xhr :post, :toggle_check, :id => next_todo.id, :_source_view => 'todo'
|
||||
next_todo.reload
|
||||
assert next_todo.completed?
|
||||
|
||||
# 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'})
|
||||
assert_equal "Call Bill Gates every day", next_todo.description
|
||||
# check that the todo is in the tickler
|
||||
assert !next_todo.show_from.nil?
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class UsersControllerTest < Test::Rails::TestCase
|
|||
get :index
|
||||
assert_response :success
|
||||
assert_equal "TRACKS::Manage Users", assigns['page_title']
|
||||
assert_equal 3, assigns['total_users']
|
||||
assert_equal 4, assigns['total_users']
|
||||
assert_equal "/users", session['return-to']
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ class UsersXmlApiTest < ActionController::IntegrationTest
|
|||
get '/users.xml', {}, basic_auth_headers()
|
||||
assert_response :success
|
||||
assert_tag :tag => "users",
|
||||
:children => { :count => 3, :only => { :tag => "user" } }
|
||||
:children => { :count => 4, :only => { :tag => "user" } }
|
||||
assert_no_tag :tag => "password"
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ assert_element_present "todo_9"
|
|||
|
||||
# add new action to existing context
|
||||
type "todo_description", "a new action"
|
||||
type "todo_context_name", "Agenda"
|
||||
type "todo_context_name", "agenda"
|
||||
click "css=#todo-form-new-action .submit_box button"
|
||||
wait_for_visible "flash"
|
||||
|
||||
|
|
|
|||
10
test/selenium/home/defer_todo_empty_context.rsel
Normal file
10
test/selenium/home/defer_todo_empty_context.rsel
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
setup :fixtures => :all
|
||||
login :as => 'admin'
|
||||
open "/"
|
||||
click "edit_icon_todo_5"
|
||||
wait_for_element_present "show_from_todo_5"
|
||||
type "show_from_todo_5", "1/1/2030"
|
||||
click "css=#submit_todo_5"
|
||||
wait_for_element_not_present "todo_5"
|
||||
assert_text 'badge_count', '9'
|
||||
wait_for_not_visible "c5"
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
setup :fixtures => :all
|
||||
login :as => 'admin'
|
||||
open "/"
|
||||
wait_for_element_present '//div[@id="line_todo_5"]//img[@alt="Defer_1"]/..'
|
||||
click '//div[@id="line_todo_5"]//img[@alt="Defer_1"]/..'
|
||||
wait_for_element_not_present "todo_5"
|
||||
assert_text 'badge_count', '9'
|
||||
wait_for_not_visible "c5"
|
||||
|
|
@ -2,4 +2,4 @@ setup :fixtures => :all
|
|||
login :as => 'admin'
|
||||
open '/'
|
||||
click "xpath=//div[@id='c1'] //div[@id='todo_9'] //input[@class='item-checkbox']"
|
||||
wait_for_element_present "xpath=//div[@id='completed'] //div[@id='todo_9']"
|
||||
wait_for_element_present "xpath=//div[@id='completed_container'] //div[@id='todo_9']"
|
||||
|
|
|
|||
|
|
@ -2,5 +2,5 @@ setup :fixtures => :all
|
|||
login :as => 'admin'
|
||||
open '/'
|
||||
click "xpath=//div[@id='c5'] //div[@id='todo_5'] //input[@class='item-checkbox']"
|
||||
wait_for_element_present "xpath=//div[@id='completed'] //div[@id='todo_5']"
|
||||
wait_for_element_present "xpath=//div[@id='completed_container'] //div[@id='todo_5']"
|
||||
wait_for_not_visible 'c5'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
setup :fixtures => :all
|
||||
login :as => 'admin'
|
||||
open '/'
|
||||
click "xpath=//div[@id='completed'] //div[@id='todo_3'] //input[@class='item-checkbox']"
|
||||
click "xpath=//div[@id='completed_container'] //div[@id='todo_3'] //input[@class='item-checkbox']"
|
||||
wait_for_element_present "xpath=//div[@id='c4'] //div[@id='todo_3']"
|
||||
assert_not_visible "c4empty-nd"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ login :as => 'admin'
|
|||
open '/m'
|
||||
wait_for_text 'css=h1 span.count', '10'
|
||||
|
||||
click_and_wait "link=Add new action"
|
||||
click_and_wait "link=0-Add new action"
|
||||
|
||||
type "todo_notes", "test notes"
|
||||
type "todo_description", "test name"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ wait_for_title "All actions"
|
|||
wait_for_text 'css=h1 span.count', '10'
|
||||
|
||||
# open context page
|
||||
click_and_wait "link=Contexts"
|
||||
click_and_wait "link=2-Contexts"
|
||||
# verify_title "All actions in context agenda"
|
||||
# choose agenda context
|
||||
click_and_wait "link=agenda"
|
||||
|
|
@ -18,7 +18,7 @@ click_and_wait "link=foo"
|
|||
verify_title "TRACKS::Tagged with 'foo'"
|
||||
wait_for_text 'css=h1 span.count', '2'
|
||||
|
||||
click_and_wait "link=Projects"
|
||||
click_and_wait "link=3-Projects"
|
||||
wait_for_text 'css=h1 span.count', '3'
|
||||
click_and_wait "link=Build a working time machine"
|
||||
wait_for_text 'css=h1 span.count', '3'
|
||||
|
|
|
|||
|
|
@ -3,5 +3,5 @@ login :as => 'admin'
|
|||
open "/projects/1"
|
||||
include_partial 'project_detail/add_deferred_todo'
|
||||
click "xpath=//div[@id='tickler'] //div[@id='todo_15'] //input[@class='item-checkbox']"
|
||||
wait_for_element_present "xpath=//div[@id='completed'] //div[@id='todo_15']"
|
||||
wait_for_element_present "xpath=//div[@id='completed_container'] //div[@id='todo_15']"
|
||||
assert_not_visible "tickler-empty-nd"
|
||||
|
|
@ -11,6 +11,6 @@ assert_text 'badge_count', '1'
|
|||
|
||||
# mark one complete
|
||||
click "xpath=//div[@id='c1'] //div[@id='todo_1'] //input[@class='item-checkbox']"
|
||||
wait_for_element_present "xpath=//div[@id='completed'] //div[@id='todo_1']"
|
||||
wait_for_element_present "xpath=//div[@id='completed_container'] //div[@id='todo_1']"
|
||||
|
||||
assert_text 'badge_count', '0'
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ class Test::Rails::TestCase < Test::Unit::TestCase
|
|||
end
|
||||
|
||||
def next_week
|
||||
1.week.from_now.utc.to_date
|
||||
1.week.from_now.utc
|
||||
end
|
||||
|
||||
# Courtesy of http://habtm.com/articles/2006/02/20/assert-yourself-man-redirecting-with-rjs
|
||||
|
|
|
|||
76
test/unit/message_gateway_test.rb
Normal file
76
test/unit/message_gateway_test.rb
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
require File.dirname(__FILE__) + '/../test_helper'
|
||||
|
||||
class MessageGatewayTest < Test::Rails::TestCase
|
||||
fixtures :users, :contexts
|
||||
|
||||
def setup
|
||||
@user = users(:sms_user)
|
||||
@inbox = contexts(:inbox)
|
||||
end
|
||||
|
||||
def load_message(filename)
|
||||
MessageGateway.receive(File.read(File.join(RAILS_ROOT, 'test', 'fixtures', filename)))
|
||||
end
|
||||
|
||||
def test_sms_with_no_subject
|
||||
todo_count = Todo.count
|
||||
|
||||
load_message('sample_sms.txt')
|
||||
# assert some stuff about it being created
|
||||
assert_equal(todo_count+1, Todo.count)
|
||||
|
||||
message_todo = Todo.find(:first, :conditions => {:description => "message_content"})
|
||||
assert_not_nil(message_todo)
|
||||
|
||||
assert_equal(@inbox, message_todo.context)
|
||||
assert_equal(@user, message_todo.user)
|
||||
end
|
||||
|
||||
def test_double_sms
|
||||
todo_count = Todo.count
|
||||
load_message('sample_sms.txt')
|
||||
load_message('sample_sms.txt')
|
||||
assert_equal(todo_count+1, Todo.count)
|
||||
end
|
||||
|
||||
def test_mms_with_subject
|
||||
todo_count = Todo.count
|
||||
|
||||
load_message('sample_mms.txt')
|
||||
|
||||
# assert some stuff about it being created
|
||||
assert_equal(todo_count+1, Todo.count)
|
||||
|
||||
message_todo = Todo.find(:first, :conditions => {:description => "This is the subject"})
|
||||
assert_not_nil(message_todo)
|
||||
|
||||
assert_equal(@inbox, message_todo.context)
|
||||
assert_equal(@user, message_todo.user)
|
||||
assert_equal("This is the message body", message_todo.notes)
|
||||
end
|
||||
|
||||
def test_no_user
|
||||
todo_count = Todo.count
|
||||
badmessage = File.read(File.join(RAILS_ROOT, 'test', 'fixtures', 'sample_sms.txt'))
|
||||
badmessage.gsub!("5555555555", "notauser")
|
||||
MessageGateway.receive(badmessage)
|
||||
assert_equal(todo_count, Todo.count)
|
||||
end
|
||||
|
||||
def test_direct_to_context
|
||||
message = File.read(File.join(RAILS_ROOT, 'test', 'fixtures', 'sample_sms.txt'))
|
||||
|
||||
valid_context_msg = message.gsub('message_content', 'this is a task @ anothercontext')
|
||||
invalid_context_msg = message.gsub('message_content', 'this is also a task @ notacontext')
|
||||
|
||||
MessageGateway.receive(valid_context_msg)
|
||||
valid_context_todo = Todo.find(:first, :conditions => {:description => "this is a task"})
|
||||
assert_not_nil(valid_context_todo)
|
||||
assert_equal(contexts(:anothercontext), valid_context_todo.context)
|
||||
|
||||
MessageGateway.receive(invalid_context_msg)
|
||||
invalid_context_todo = Todo.find(:first, :conditions => {:description => 'this is also a task'})
|
||||
assert_not_nil(invalid_context_todo)
|
||||
assert_equal(@inbox, invalid_context_todo.context)
|
||||
end
|
||||
end
|
||||
|
|
@ -20,7 +20,7 @@ class PreferenceTest < Test::Rails::TestCase
|
|||
end
|
||||
|
||||
def test_parse_date
|
||||
assert_equal Date.new(2007, 5, 20).to_s, @admin_user.preference.parse_date('20/5/2007').to_s
|
||||
assert_equal @admin_user.at_midnight(Date.new(2007, 5, 20)).to_s, @admin_user.preference.parse_date('20/5/2007').to_s
|
||||
end
|
||||
|
||||
def test_parse_date_returns_nil_if_string_is_empty
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ class ProjectTest < Test::Rails::TestCase
|
|||
def test_deferred_todos
|
||||
assert_equal 1, @timemachine.deferred_todos.size
|
||||
t = @timemachine.not_done_todos[0]
|
||||
t.show_from = 1.days.from_now.utc.to_date
|
||||
t.show_from = 1.days.from_now.utc
|
||||
t.save!
|
||||
assert_equal 2, Project.find(@timemachine.id).deferred_todos.size
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,13 +15,13 @@ class RecurringTodoTest < Test::Rails::TestCase
|
|||
@in_three_days = Time.now.utc + 3.days
|
||||
@in_four_days = @in_three_days + 1.day # need a day after start_from
|
||||
|
||||
@friday = Time.utc(2008,6,6)
|
||||
@saturday = Time.utc(2008,6,7)
|
||||
@sunday = Time.utc(2008,6,8) # june 8, 2008 was a sunday
|
||||
@monday = Time.utc(2008,6,9)
|
||||
@tuesday = Time.utc(2008,6,10)
|
||||
@wednesday = Time.utc(2008,6,11)
|
||||
@thursday = Time.utc(2008,6,12)
|
||||
@friday = Time.zone.local(2008,6,6)
|
||||
@saturday = Time.zone.local(2008,6,7)
|
||||
@sunday = Time.zone.local(2008,6,8) # june 8, 2008 was a sunday
|
||||
@monday = Time.zone.local(2008,6,9)
|
||||
@tuesday = Time.zone.local(2008,6,10)
|
||||
@wednesday = Time.zone.local(2008,6,11)
|
||||
@thursday = Time.zone.local(2008,6,12)
|
||||
end
|
||||
|
||||
def test_pattern_text
|
||||
|
|
@ -134,19 +134,19 @@ class RecurringTodoTest < Test::Rails::TestCase
|
|||
|
||||
def test_monthly_pattern
|
||||
due_date = @monthly_every_last_friday.get_due_date(@sunday)
|
||||
assert_equal Time.utc(2008,6,27), due_date
|
||||
assert_equal Time.zone.local(2008,6,27), due_date
|
||||
|
||||
friday_is_last_day_of_month = Time.utc(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 )
|
||||
assert_equal friday_is_last_day_of_month , due_date
|
||||
|
||||
@monthly_every_third_friday = @monthly_every_last_friday
|
||||
@monthly_every_third_friday.every_other3=3 #third
|
||||
due_date = @monthly_every_last_friday.get_due_date(@sunday) # june 8th 2008
|
||||
assert_equal Time.utc(2008, 6, 20), due_date
|
||||
assert_equal Time.zone.local(2008, 6, 20), due_date
|
||||
# set date past third friday of this month
|
||||
due_date = @monthly_every_last_friday.get_due_date(Time.utc(2008,6,21)) # june 21th 2008
|
||||
assert_equal Time.utc(2008, 8, 15), due_date # every 2 months, so aug
|
||||
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
|
||||
|
||||
@monthly = @monthly_every_last_friday
|
||||
@monthly.recurrence_selector=0
|
||||
|
|
@ -157,12 +157,12 @@ class RecurringTodoTest < Test::Rails::TestCase
|
|||
assert_equal @sunday, due_date # june 8th
|
||||
|
||||
due_date = @monthly.get_due_date(@sunday) # june 8th
|
||||
assert_equal Time.utc(2008,8,8), due_date # aug 8th
|
||||
assert_equal Time.zone.local(2008,8,8), due_date # aug 8th
|
||||
end
|
||||
|
||||
def test_yearly_pattern
|
||||
# beginning of same year
|
||||
due_date = @yearly.get_due_date(Time.utc(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
|
||||
|
||||
# same month, previous date
|
||||
|
|
@ -173,20 +173,20 @@ class RecurringTodoTest < Test::Rails::TestCase
|
|||
|
||||
# same month, day after
|
||||
due_date = @yearly.get_due_date(@monday) # june 9th
|
||||
assert_equal Time.utc(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.every_other3 = 2 # second
|
||||
@yearly.every_count = 3 # wednesday
|
||||
# beginning of same year
|
||||
due_date = @yearly.get_due_date(Time.utc(2008,2,10)) # feb 10th
|
||||
assert_equal Time.utc(2008,6,11), due_date # june 11th
|
||||
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
|
||||
# same month, before second wednesday
|
||||
due_date = @yearly.get_due_date(@saturday) # june 7th
|
||||
assert_equal Time.utc(2008,6,11), due_date # june 11th
|
||||
assert_equal Time.zone.local(2008,6,11), due_date # june 11th
|
||||
# same month, after second wednesday
|
||||
due_date = @yearly.get_due_date(Time.utc(2008,6,12)) # june 7th
|
||||
assert_equal Time.utc(2009,6,10), due_date # june 10th
|
||||
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
|
||||
|
||||
# test handling of nil
|
||||
due_date1 = @yearly.get_due_date(nil)
|
||||
|
|
@ -207,24 +207,24 @@ class RecurringTodoTest < Test::Rails::TestCase
|
|||
due_date = @every_day.get_due_date(@in_four_days)
|
||||
assert_equal @in_four_days+1.day, due_date
|
||||
|
||||
@weekly_every_day.start_from = Time.utc(2020,1,1)
|
||||
assert_equal Time.utc(2020,1,1), @weekly_every_day.get_due_date(nil)
|
||||
assert_equal Time.utc(2020,1,1), @weekly_every_day.get_due_date(Time.utc(2019,10,1))
|
||||
assert_equal Time.utc(2020,1,10), @weekly_every_day.get_due_date(Time.utc(2020,1,9))
|
||||
@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(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))
|
||||
|
||||
@monthly_every_last_friday.start_from = Time.utc(2020,1,1)
|
||||
assert_equal Time.utc(2020,1,31), @monthly_every_last_friday.get_due_date(nil) # last friday of jan
|
||||
assert_equal Time.utc(2020,1,31), @monthly_every_last_friday.get_due_date(Time.utc(2019,12,1)) # last friday of jan
|
||||
assert_equal Time.utc(2020,2,28), @monthly_every_last_friday.get_due_date(Time.utc(2020,2,1)) # last friday of feb
|
||||
@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(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
|
||||
|
||||
# start from after june 8th 2008
|
||||
@yearly.start_from = Time.utc(2020,6,12)
|
||||
assert_equal Time.utc(2021,6,8), @yearly.get_due_date(nil) # jun 8th next year
|
||||
assert_equal Time.utc(2021,6,8), @yearly.get_due_date(Time.utc(2019,6,1)) # also next year
|
||||
assert_equal Time.utc(2021,6,8), @yearly.get_due_date(Time.utc(2020,6,15)) # also next year
|
||||
@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(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
|
||||
|
||||
this_year = Time.now.utc.year
|
||||
@yearly.start_from = Time.utc(this_year+1,6,12)
|
||||
@yearly.start_from = Time.zone.local(this_year+1,6,12)
|
||||
due_date = @yearly.get_due_date(nil)
|
||||
assert_equal due_date.year, this_year+2
|
||||
end
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class TodoCreateParamsHelperTest < Test::Rails::TestCase
|
|||
end
|
||||
|
||||
def test_show_from_accessor
|
||||
expected_date = Time.now.to_date
|
||||
expected_date = Time.now
|
||||
params = { 'todo' => { 'show_from' => expected_date}}
|
||||
prefs = flexmock()
|
||||
params_helper = TodosController::TodoCreateParamsHelper.new(params, prefs)
|
||||
|
|
@ -26,7 +26,7 @@ class TodoCreateParamsHelperTest < Test::Rails::TestCase
|
|||
end
|
||||
|
||||
def test_due_accessor
|
||||
expected_date = Time.now.to_date
|
||||
expected_date = Time.now
|
||||
params = { 'todo' => { 'due' => expected_date}}
|
||||
prefs = flexmock()
|
||||
params_helper = TodosController::TodoCreateParamsHelper.new(params, prefs)
|
||||
|
|
|
|||
|
|
@ -68,9 +68,8 @@ class TodoTest < Test::Rails::TestCase
|
|||
|
||||
def test_validate_show_from_must_be_a_date_in_the_future
|
||||
t = @not_completed2
|
||||
t[:show_from] = 1.week.ago.to_date # 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
|
||||
assert_equal 1.week.ago.to_date, t.show_from
|
||||
assert !t.save
|
||||
assert_equal 1, t.errors.count
|
||||
assert_equal "must be a date in the future", t.errors.on(:show_from)
|
||||
|
|
@ -118,7 +117,7 @@ class TodoTest < Test::Rails::TestCase
|
|||
|
||||
def test_activate_also_saves
|
||||
t = @not_completed1
|
||||
t.show_from = 1.week.from_now.to_date
|
||||
t.show_from = 1.week.from_now
|
||||
t.save!
|
||||
assert t.deferred?
|
||||
t.reload
|
||||
|
|
|
|||
|
|
@ -235,7 +235,7 @@ class UserTest < Test::Rails::TestCase
|
|||
|
||||
def test_find_and_activate_deferred_todos_that_are_ready
|
||||
assert_equal 1, @admin_user.deferred_todos.count
|
||||
@admin_user.deferred_todos[0].show_from = @admin_user.time.to_date
|
||||
@admin_user.deferred_todos[0].show_from = Time.now.utc - 5.seconds
|
||||
@admin_user.deferred_todos[0].save
|
||||
@admin_user.deferred_todos.reload
|
||||
@admin_user.deferred_todos.find_and_activate_ready
|
||||
|
|
|
|||
|
|
@ -20,28 +20,28 @@ class TodosHelperTest < Test::Rails::HelperTestCase
|
|||
end
|
||||
|
||||
def test_show_date_in_past
|
||||
date = 3.days.ago.to_date
|
||||
date = 3.days.ago
|
||||
html = show_date(date)
|
||||
formatted_date = format_date(date)
|
||||
assert_equal %Q{<a title="#{formatted_date}"><span class="red">Scheduled to show 3 days ago</span></a> }, html
|
||||
end
|
||||
|
||||
def test_show_date_today
|
||||
date = Time.zone.now.to_date
|
||||
date = Time.zone.now
|
||||
html = show_date(date)
|
||||
formatted_date = format_date(date)
|
||||
assert_equal %Q{<a title="#{formatted_date}"><span class="amber">Show Today</span></a> }, html
|
||||
end
|
||||
|
||||
def test_show_date_tomorrow
|
||||
date = 1.day.from_now.to_date
|
||||
date = 1.day.from_now
|
||||
html = show_date(date)
|
||||
formatted_date = format_date(date)
|
||||
assert_equal %Q{<a title="#{formatted_date}"><span class="amber">Show Tomorrow</span></a> }, html
|
||||
end
|
||||
|
||||
def test_show_date_future
|
||||
date = 10.days.from_now.to_date
|
||||
date = 10.days.from_now
|
||||
html = show_date(date)
|
||||
formatted_date = format_date(date)
|
||||
assert_equal %Q{<a title="#{formatted_date}"><span class="green">Show in 10 days</span></a> }, html
|
||||
|
|
@ -49,20 +49,22 @@ class TodosHelperTest < Test::Rails::HelperTestCase
|
|||
|
||||
def test_remote_star_icon_unstarred
|
||||
@todo = flexmock(:id => 1, :to_param => 1, :description => 'Get gas', :starred? => false)
|
||||
assert_remote_star_icon_helper_matches %r{<a href="/todos/1/toggle_star" class="icon star_item" title="star the action 'Get gas'"><img alt="Blank" class="unstarred_todo" src="/images/blank.png[?0-9]*" title="Star action" /></a>}
|
||||
# added dot (.) to regexp because somehouw the extra dot is added in the tests while its not in the rendered html
|
||||
assert_remote_star_icon_helper_matches %r{<a href="/todos/1/toggle_star" class="icon star_item" title="star the action 'Get gas'"><img alt="Blank" class="unstarred_todo" src="/images/blank.png[.?0-9]*" title="Star action" /></a>}
|
||||
assert_behavior_registered
|
||||
end
|
||||
|
||||
def test_remote_star_icon_starred
|
||||
@todo = flexmock(:id => 1, :to_param => 1, :description => 'Get gas', :starred? => true)
|
||||
assert_remote_star_icon_helper_matches %r{<a href="/todos/1/toggle_star" class="icon star_item" title="star the action 'Get gas'"><img alt="Blank" class="starred_todo" src="/images/blank.png[?0-9]*" title="Star action" /></a>}
|
||||
# added dot (.) to regexp because somehouw the extra dot is added in the tests while its not in the rendered html
|
||||
assert_remote_star_icon_helper_matches %r{<a href="/todos/1/toggle_star" class="icon star_item" title="star the action 'Get gas'"><img alt="Blank" class="starred_todo" src="/images/blank.png[.?0-9]*" title="Star action" /></a>}
|
||||
assert_behavior_registered
|
||||
end
|
||||
|
||||
def assert_remote_star_icon_helper_matches(regex)
|
||||
@controller.send :initialise_js_behaviours #simulate before filter
|
||||
output = remote_star_icon
|
||||
#puts output
|
||||
# puts output
|
||||
assert output =~ regex
|
||||
@controller.send :store_js_behaviours #simulate after filter
|
||||
end
|
||||
|
|
@ -74,7 +76,7 @@ class TodosHelperTest < Test::Rails::HelperTestCase
|
|||
rule = behaviors[:rules][0]
|
||||
assert_equal ".item-container a.star_item:click", rule[0]
|
||||
assert_equal "new Ajax.Request(this.href, {asynchronous:true, evalScripts:true, method:'put', parameters:{ _source_view : '' }})\n; return false;",
|
||||
rule[1]
|
||||
rule[1]
|
||||
end
|
||||
|
||||
def protect_against_forgery?
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue