diff --git a/app/controllers/application.rb b/app/controllers/application.rb index b08dc0be..b24dfd54 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -115,7 +115,7 @@ class ApplicationController < ActionController::Base def format_date(date) if date date_format = prefs.date_format - formatted_date = date.strftime("#{date_format}") + formatted_date = date.in_time_zone(prefs.time_zone).strftime("#{date_format}") else formatted_date = '' end @@ -170,11 +170,12 @@ class ApplicationController < ActionController::Base # set dates todo.recurring_todo_id = rt.id todo.due = rt.get_due_date(date) - # make sure that show_from is not in the past + show_from_date = rt.get_show_from_date(date) if show_from_date.nil? todo.show_from=nil else + # make sure that show_from is not in the past todo.show_from = show_from_date < Time.zone.now ? nil : show_from_date end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index bf84e44d..b2489c7d 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -186,6 +186,13 @@ class ProjectsController < ApplicationController init_not_done_counts(['project']) end + def actionize + @state = params['state'] + @projects = current_user.projects.actionize(current_user.id, :state => @state) if @state + @contexts = current_user.contexts + init_not_done_counts(['project']) + end + protected def render_projects_html diff --git a/app/controllers/todos_controller.rb b/app/controllers/todos_controller.rb index ada43168..aead74a1 100644 --- a/app/controllers/todos_controller.rb +++ b/app/controllers/todos_controller.rb @@ -10,6 +10,7 @@ class TodosController < ApplicationController session :off, :only => :index, :if => Proc.new { |req| is_feed_request(req) } def index + current_user.deferred_todos.find_and_activate_ready @projects = current_user.projects.find(:all, :include => [:default_context]) @contexts = current_user.contexts.find(:all) @@ -133,7 +134,7 @@ class TodosController < ApplicationController @saved = @todo.toggle_completion! # check if this todo has a related recurring_todo. If so, create next todo - check_for_next_todo if @saved + @new_recurring_todo = check_for_next_todo(@todo) if @saved respond_to do |format| format.js do @@ -278,7 +279,7 @@ class TodosController < ApplicationController @project_id = @todo.project_id # check if this todo has a related recurring_todo. If so, create next todo - check_for_next_todo + @new_recurring_todo = check_for_next_todo(@todo) @saved = @todo.destroy @@ -382,7 +383,7 @@ class TodosController < ApplicationController @not_done_todos = tag_collection.find(:all, :conditions => ['taggings.user_id = ? and state = ?', current_user.id, 'active'], - :order => 'todos.completed_at DESC, todos.created_at DESC') + :order => 'todos.due IS NULL, todos.due ASC, todos.created_at ASC') @hidden_todos = current_user.todos.find(:all, :include => [:taggings, :tags, :context], :conditions => ['tags.name = ? AND (todos.state = ? OR (contexts.hide = ? AND todos.state = ?))', @tag_name, 'project_hidden', true, 'active'], @@ -390,6 +391,7 @@ class TodosController < ApplicationController @deferred = tag_collection.find(:all, :conditions => ['taggings.user_id = ? and state = ?', current_user.id, 'deferred'], :order => 'show_from ASC, todos.created_at DESC') + # If you've set no_completed to zero, the completed items box isn't shown on # the tag page max_completed = current_user.prefs.show_number_completed @@ -770,33 +772,34 @@ class TodosController < ApplicationController ['rss','atom','txt','ics'].include?(req.parameters[:format]) end - def check_for_next_todo + def check_for_next_todo(todo) # check if this todo has a related recurring_todo. If so, create next todo - @new_recurring_todo = nil - @recurring_todo = nil - if @todo.from_recurring_todo? - @recurring_todo = current_user.recurring_todos.find(@todo.recurring_todo_id) + new_recurring_todo = nil + 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 = todo.due.nil? ? todo.show_from : todo.due - # 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 both due and show_from are nil, check for a next todo from now + date_to_check = Time.zone.now if date_to_check.nil? - if @recurring_todo.active? && @recurring_todo.has_next_todo(date_to_check) + if recurring_todo.active? && recurring_todo.has_next_todo(date_to_check) # 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. Discard the time part in the compare. We pick yesterday so that + # new todos due for today will be created instead of new todos for + # tomorrow. 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) + new_recurring_todo = create_todo_from_recurring_todo(recurring_todo, date) end - end + end + return new_recurring_todo end def get_due_id_for_calendar(due) diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index b989b432..aee59c90 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -281,7 +281,7 @@ module TodosHelper 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 "")} + link_to_remote image_tag("defer_#{days}.png", :alt => "Defer #{pluralize(days, 'day')}"), :url => {:controller => 'todos', :action => 'defer', :id => @todo.id, :days => days, :_source_view => (@source_view.underscore.gsub(/\s+/,'_') rescue "")} end end diff --git a/app/models/recurring_todo.rb b/app/models/recurring_todo.rb index 86b24dae..fdd230e7 100644 --- a/app/models/recurring_todo.rb +++ b/app/models/recurring_todo.rb @@ -495,12 +495,14 @@ class RecurringTodo < ActiveRecord::Base def get_xth_day_of_month(x, weekday, month, year) if x == 5 - # last -> count backwards - last_day = Time.zone.local(year, month, Time.days_in_month(month)) + # last -> count backwards. use UTC to avoid strange timezone oddities + # where last_day -= 1.day seems to shift tz+0100 to tz+0000 + last_day = Time.utc(year, month, Time.days_in_month(month)) while last_day.wday != weekday last_day -= 1.day end - return last_day + # convert back to local timezone + return Time.zone.local(last_day.year, last_day.month, last_day.day) else # 1-4th -> count upwards start = Time.zone.local(year,month,1) @@ -517,17 +519,19 @@ class RecurringTodo < ActiveRecord::Base end def get_yearly_date(previous) - start = determine_start(previous) day = self.every_other1 month = self.every_other2 case self.recurrence_selector 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.zone.local(start.year+1, month, 1) if start.day >= day - start = Time.zone.local(start.year, month, 1) if start.day <= day + if start.month > month || (start.month == month && start.day >= day) + # if there is no next month n and day m in this year, search in next + # year + start = Time.zone.local(start.year+1, month, 1) + else + # if there is a next month n, stay in this year + start = Time.zone.local(start.year, month, 1) end return Time.zone.local(start.year, month, day) diff --git a/app/models/user.rb b/app/models/user.rb index bf84bbd3..8a85b64b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -51,6 +51,21 @@ class User < ActiveRecord::Base self.update_positions(projects.map{ |p| p.id }) return projects end + def actionize(user_id, scope_conditions = {}) + @state = scope_conditions[:state] + query_state = "" + query_state = "AND project.state = '" + @state +"' "if @state + projects = Project.find_by_sql([ + "SELECT project.id, count(todo.id) as p_count " + + "FROM projects as project " + + "LEFT OUTER JOIN todos as todo ON todo.project_id = project.id "+ + "WHERE project.user_id = ? AND NOT todo.state='completed' " + + query_state + + " GROUP BY project.id ORDER by p_count DESC",user_id]) + self.update_positions(projects.map{ |p| p.id }) + projects = find(:all, :conditions => scope_conditions) + return projects + end end has_many :active_projects, :class_name => 'Project', diff --git a/app/views/integrations/_applescript2.rhtml b/app/views/integrations/_applescript2.rhtml index d63ee561..e883f998 100644 --- a/app/views/integrations/_applescript2.rhtml +++ b/app/views/integrations/_applescript2.rhtml @@ -8,9 +8,9 @@ the newly created action. *) (* Edit appropriately for your setup *) -property myUsername to "<%= current_user.login %>" -property myToken to "<%= current_user.token %>" -property myContextID to <%= context.id %> (* <%= context.name %> *) +property myUsername : "<%= current_user.login %>" +property myToken : "<%= current_user.token %>" +property myContextID : <%= context.id %> (* <%= context.name %> *) -- this string is used when the message subject is empty property emptySubject : "No Subject Specified" diff --git a/app/views/projects/_project_state_group.rhtml b/app/views/projects/_project_state_group.rhtml index 6fe6ef21..32693f0f 100644 --- a/app/views/projects/_project_state_group.rhtml +++ b/app/views/projects/_project_state_group.rhtml @@ -1,7 +1,8 @@
>

<%= project_state_group.length %><%= state.titlecase %> Projects

+
<%= render :partial => 'project_listing', :collection => project_state_group %>
<%= sortable_element "list-#{state}-projects", get_listing_sortable_options("list-#{state}-projects") %> -
\ No newline at end of file + diff --git a/app/views/projects/actionize.js.rjs b/app/views/projects/actionize.js.rjs new file mode 100644 index 00000000..ccb10451 --- /dev/null +++ b/app/views/projects/actionize.js.rjs @@ -0,0 +1,6 @@ +list_id = "list-#{@state}-projects" +page.replace_html list_id, + :partial => 'project_listing', + :collection => @projects +page.sortable list_id, get_listing_sortable_options(list_id) + \ No newline at end of file diff --git a/app/views/todos/_edit_mobile.rhtml b/app/views/todos/_edit_mobile.rhtml index f1caa191..0837f026 100644 --- a/app/views/todos/_edit_mobile.rhtml +++ b/app/views/todos/_edit_mobile.rhtml @@ -33,8 +33,8 @@ end %>

<%= text_field_tag "tag_list", @tag_list_text, :size => 30, :tabindex => 6 %>

-<%= date_select("todo", "due", :order => [:day, :month, :year], - :start_year => this_year, :include_blank => true) %> +<%= date_select("todo", "due", {:order => [:day, :month, :year], + :start_year => this_year, :include_blank => true}, :tabindex => 7) %>

-<%= date_select("todo", "show_from", :order => [:day, :month, :year], - :start_year => this_year, :include_blank => true) %> +<%= date_select("todo", "show_from", {:order => [:day, :month, :year], + :start_year => this_year, :include_blank => true}, :tabindex => 8) %> diff --git a/app/views/todos/new.m.erb b/app/views/todos/new.m.erb index 2dfe0fd7..361e223d 100644 --- a/app/views/todos/new.m.erb +++ b/app/views/todos/new.m.erb @@ -1,5 +1,5 @@ <% form_tag formatted_todos_path(:m), :method => :post do %> <%= render :partial => 'edit_mobile' %> -

+

<% end -%> <%= link_to "Back", @return_path %> \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 50e29ff7..1b8bf1c3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -28,6 +28,10 @@ ActionController::Routing::Routes.draw do |map| projects.resources :todos, :name_prefix => "project_" end + map.resources :projects, :collection => {:order => :post, :actionize => :post} do |projects| + projects.resources :todos, :name_prefix => "project_" + end + map.resources :todos, :member => {:toggle_check => :put, :toggle_star => :put}, :collection => {:check_deferred => :post, :filter_to_context => :post, :filter_to_project => :post} diff --git a/db/migrate/042_change_dates_to_datetimes.rb b/db/migrate/042_change_dates_to_datetimes.rb index 99e75179..d534c642 100644 --- a/db/migrate/042_change_dates_to_datetimes.rb +++ b/db/migrate/042_change_dates_to_datetimes.rb @@ -6,14 +6,26 @@ class ChangeDatesToDatetimes < ActiveRecord::Migration change_column :recurring_todos, :end_date, :datetime User.all(:include => [:todos, :recurring_todos]).each do |user| + if !user.prefs ## ugly hack for strange edge-case of not having preferences object + user.instance_eval do + def at_midnight(date) + return Time.zone.local(date.year, date.month, date.day, 0, 0, 0) + end + def time + Time.zone.now + end + end + end 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? + todo[:show_from] = user.at_midnight(todo.show_from) unless todo.show_from.nil? + todo[:due] = user.at_midnight(todo.due) unless todo.due.nil? + todo.save_with_validation(false) 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? + todo[:start_from] = user.at_midnight(todo.start_from) unless todo.start_from.nil? + todo[:end_date] = user.at_midnight(todo.end_date) unless todo.end_date.nil? + todo.save_with_validation(false) end end end diff --git a/doc/CHANGELOG b/doc/CHANGELOG index 05f59965..7cb7dcfc 100644 --- a/doc/CHANGELOG +++ b/doc/CHANGELOG @@ -22,6 +22,7 @@ New features: 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) +7. You can now sort projects on number of active todos Under the hood: 1. Move selenium tests to RSpec diff --git a/public/javascripts/tracks_1220755978.js b/public/javascripts/tracks_1222451275.js similarity index 100% rename from public/javascripts/tracks_1220755978.js rename to public/javascripts/tracks_1222451275.js diff --git a/public/stylesheets/standard.css b/public/stylesheets/standard.css index 3b31fcd7..ea8f0dc5 100644 --- a/public/stylesheets/standard.css +++ b/public/stylesheets/standard.css @@ -692,11 +692,14 @@ div#list-active-projects, div#list-hidden-projects, div#list-completed-projects, margin:20px 0px 8px 13px } -div.alpha_sort { +div.menu_sort { margin-top:-20px; float:right; } +div.alpha_sort, div.tasks_sort,span.sort_separator { + float:left; +} .container td { border: none; diff --git a/public/stylesheets/tracks_1220796176.css b/public/stylesheets/tracks_1225223947.css similarity index 99% rename from public/stylesheets/tracks_1220796176.css rename to public/stylesheets/tracks_1225223947.css index 446d9b87..8afe55d2 100644 --- a/public/stylesheets/tracks_1220796176.css +++ b/public/stylesheets/tracks_1225223947.css @@ -129,7 +129,8 @@ div.buttons, div.buttons a, div.buttons a:hover {text-align: right; margin-right div#list-active-projects, div#list-hidden-projects, div#list-completed-projects, div#list-contexts, div#projects-empty-nd {clear:right; border: 1px solid #999} .project-state-group h2 {margin:20px 0px 8px 13px} .search-result-group h2 {margin:20px 0px 8px 13px } -div.alpha_sort {margin-top:-20px; float:right} +div.menu_sort {margin-top:-20px; float:right} +div.alpha_sort, div.tasks_sort,span.sort_separator {float:left} .container td {border: none; padding-bottom: 5px} .container form {border: none} div.project_description {background: #eee; padding: 5px; margin-top: 0px; margin-left: -5px; margin-right: -5px; color: #666; font-style: italic; font-size: 12px; font-weight: normal} diff --git a/test/fixtures/todos.yml b/test/fixtures/todos.yml index 52db71ea..f4941c0c 100644 --- a/test/fixtures/todos.yml +++ b/test/fixtures/todos.yml @@ -230,3 +230,17 @@ end completed_at: ~ show_from: <%= next_week %> user_id: 2 + +18: + id: 18 + user_id: 1 + context_id: 1 + project_id: 2 + description: Call Bill Gates every day + notes: ~ + state: active + created_at: <%= last_week %> + due: <%= last_week %> + completed_at: ~ + show_from: ~ + recurring_todo_id: 1 \ No newline at end of file diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb index f97a892a..ee3df3e4 100644 --- a/test/functional/projects_controller_test.rb +++ b/test/functional/projects_controller_test.rb @@ -218,6 +218,15 @@ class ProjectsControllerTest < TodoContainerControllerTestBase get :index, { :format => "txt", :token => users(:admin_user).token } assert_response :ok end + + def test_actionize_sorts_active_projects_by_number_of_tasks + login_as :admin_user + u = users(:admin_user) + post :actionize, :state => "active", :format => 'js' + assert_equal 1, projects(:gardenclean).position + assert_equal 2, projects(:timemachine).position + assert_equal 3, projects(:moremoney).position + end def test_alphabetize_sorts_active_projects_alphabetically login_as :admin_user diff --git a/test/functional/recurring_todos_controller_test.rb b/test/functional/recurring_todos_controller_test.rb index e9485c5b..2bd67e67 100644 --- a/test/functional/recurring_todos_controller_test.rb +++ b/test/functional/recurring_todos_controller_test.rb @@ -79,6 +79,11 @@ class RecurringTodosControllerTest < ActionController::TestCase recurring_todo_1 = RecurringTodo.find(1) assert recurring_todo_1.completed? + # remove remaining todo + todo = Todo.find_by_recurring_todo_id(1) + todo.recurring_todo_id = 2 + todo.save + todo_count = Todo.count # mark as active @@ -120,4 +125,59 @@ class RecurringTodosControllerTest < ActionController::TestCase assert_equal nil, new_todo.show_from end + def test_last_sunday_of_march + # this test is a duplicate of the unit test. Only this test covers the + # codepath in the controllers + + login_as(:admin_user) + + orig_rt_count = RecurringTodo.count + orig_todo_count = Todo.count + + put :create, + "context_name"=>"library", + "project_name"=>"Build a working time machine", + "recurring_todo" => + { + "daily_every_x_days"=>"1", + "daily_selector"=>"daily_every_x_day", + "description"=>"new recurring pattern", + "end_date" => "", + "ends_on" => "no_end_date", + "monthly_day_of_week" => "1", + "monthly_every_x_day" => "22", + "monthly_every_x_month2" => "1", + "monthly_every_x_month" => "1", + "monthly_every_xth_day"=>"1", + "monthly_selector"=>"monthly_every_x_day", + "notes"=>"with some notes", + "number_of_occurences" => "", + "recurring_period"=>"yearly", + "recurring_show_days_before"=>"0", + "recurring_target"=>"due_date", + "start_from"=>"", + "weekly_every_x_week"=>"1", + "weekly_return_monday"=>"w", + "yearly_day_of_week"=>"0", + "yearly_every_x_day"=>"22", + "yearly_every_xth_day"=>"5", + "yearly_month_of_year2"=>"3", + "yearly_month_of_year"=>"10", + "yearly_selector"=>"yearly_every_xth_day" + }, + "tag_list"=>"one, two, three, four" + + # check new recurring todo added + assert_equal orig_rt_count+1, RecurringTodo.count + # check new todo added + assert_equal orig_todo_count+1, Todo.count + + # find the newly created todo + new_todo = Todo.find_by_description("new recurring pattern") + assert !new_todo.nil? + + # the date should be 29 march 2009 + assert_equal Time.zone.local(2009,3,29), new_todo.due + end + end diff --git a/test/functional/stats_controller_test.rb b/test/functional/stats_controller_test.rb index a3cf9645..f3f2c64a 100755 --- a/test/functional/stats_controller_test.rb +++ b/test/functional/stats_controller_test.rb @@ -56,7 +56,7 @@ class StatsControllerTest < Test::Unit::TestCase assert_equal 3, assigns['projects'].count assert_equal 3, assigns['projects'].count(:conditions => "state = 'active'") assert_equal 10, assigns['contexts'].count - assert_equal 15, assigns['actions'].count + assert_equal 16, assigns['actions'].count assert_equal 4, assigns['tags'].count assert_equal 2, assigns['unique_tags'].size assert_equal 2.week.ago.utc.beginning_of_day, assigns['first_action'].created_at diff --git a/test/functional/todos_controller_test.rb b/test/functional/todos_controller_test.rb index cd4fba01..9804b0b5 100644 --- a/test/functional/todos_controller_test.rb +++ b/test/functional/todos_controller_test.rb @@ -124,9 +124,9 @@ class TodosControllerTest < Test::Rails::TestCase def test_update_todo_to_deferred_is_reflected_in_badge_count login_as(:admin_user) get :index - assert_equal 10, assigns['count'] + assert_equal 11, assigns['count'] xhr :post, :update, :id => 1, :_source_view => 'todo', "context_name"=>"library", "project_name"=>"Make more money than Billy Gates", "todo"=>{"id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"30/11/2006", "show_from"=>"30/11/2030"}, "tag_list"=>"foo bar" - assert_equal 9, assigns['down_count'] + assert_equal 10, assigns['down_count'] end def test_update_todo @@ -188,7 +188,7 @@ class TodosControllerTest < Test::Rails::TestCase assert_select '>description', "Actions for #{users(:admin_user).display_name}" assert_select 'language', 'en-us' assert_select 'ttl', '40' - assert_select 'item', 10 do + assert_select 'item', 11 do assert_select 'title', /.+/ assert_select 'description', /.*/ assert_select 'link', %r{http://test.host/contexts/.+} @@ -242,7 +242,7 @@ class TodosControllerTest < Test::Rails::TestCase assert_xml_select 'feed[xmlns="http://www.w3.org/2005/Atom"]' do assert_xml_select '>title', 'Tracks Actions' assert_xml_select '>subtitle', "Actions for #{users(:admin_user).display_name}" - assert_xml_select 'entry', 10 do + assert_xml_select 'entry', 11 do assert_xml_select 'title', /.+/ assert_xml_select 'content[type="html"]', /.*/ assert_xml_select 'published', /(#{Regexp.escape(projects(:timemachine).updated_at.xmlschema)}|#{Regexp.escape(projects(:moremoney).updated_at.xmlschema)})/ @@ -311,7 +311,7 @@ class TodosControllerTest < Test::Rails::TestCase def test_mobile_index_assigns_down_count login_as(:admin_user) get :index, { :format => "m" } - assert_equal 10, assigns['down_count'] + assert_equal 11, assigns['down_count'] end def test_mobile_create_action_creates_a_new_todo @@ -362,38 +362,127 @@ class TodosControllerTest < Test::Rails::TestCase # link todo_1 and recurring_todo_1 recurring_todo_1 = RecurringTodo.find(1) - todo_1 = Todo.find(1) - todo_1.recurring_todo_id = recurring_todo_1.id - - # update todo_1 - assert todo_1.save + todo_1 = Todo.find_by_recurring_todo_id(1) # mark todo_1 as complete by toggle_check - xhr :post, :toggle_check, :id => 1, :_source_view => 'todo' + xhr :post, :toggle_check, :id => todo_1.id, :_source_view => 'todo' todo_1.reload assert todo_1.completed? + # check that there is only one active todo belonging to recurring_todo + count = Todo.count(:all, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'active'}) + assert_equal 1, count + # check there is a new todo linked to the recurring pattern next_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'active'}) assert_equal "Call Bill Gates every day", next_todo.description + # check that the new todo is not the same as todo_1 + assert_not_equal todo_1.id, next_todo.id - # 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 + # change recurrence pattern to monthly and set show_from 2 days before due + # date this forces the next todo to be put in the tickler recurring_todo_1.show_from_delta = 2 - recurring_todo_1.recurring_period = 'weekly' - recurring_todo_1.every_day = 'smtwtfs' + recurring_todo_1.recurring_period = 'monthly' + recurring_todo_1.recurrence_selector = 0 + recurring_todo_1.every_other1 = 1 + recurring_todo_1.every_other2 = 2 + recurring_todo_1.every_other3 = 5 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 that there are three todos belonging to recurring_todo: two + # completed and one deferred + count = Todo.count(:all, :conditions => {:recurring_todo_id => recurring_todo_1.id}) + assert_equal 3, count + # 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 !next_todo.nil? 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 - + + def test_toggle_check_on_rec_todo_show_from_today + login_as(:admin_user) + + # link todo_1 and recurring_todo_1 + recurring_todo_1 = RecurringTodo.find(1) + todo_1 = Todo.find_by_recurring_todo_id(1) + today = Time.now.utc.at_midnight + + # change recurrence pattern to monthly and set show_from to today + recurring_todo_1.target = 'show_from_date' + recurring_todo_1.recurring_period = 'monthly' + recurring_todo_1.recurrence_selector = 0 + recurring_todo_1.every_other1 = today.day + recurring_todo_1.every_other2 = 1 + recurring_todo_1.save + + # mark todo_1 as complete by toggle_check, this gets rid of todo_1 that was + # not correctly created from the adjusted recurring pattern we defined + # above. + xhr :post, :toggle_check, :id => todo_1.id, :_source_view => 'todo' + todo_1.reload + assert todo_1.completed? + + # locate the new todo. This todo is created from the adjusted recurring + # pattern defined in this test + new_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'active'}) + assert !new_todo.nil? + + # mark new_todo as complete by toggle_check + xhr :post, :toggle_check, :id => new_todo.id, :_source_view => 'todo' + new_todo.reload + assert todo_1.completed? + + # locate the new todo in tickler + new_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'deferred'}) + assert !new_todo.nil? + + assert_equal "Call Bill Gates every day", new_todo.description + # check that the new todo is not the same as todo_1 + assert_not_equal todo_1.id, new_todo.id + + # check that the new_todo is in the tickler to show next month + assert !new_todo.show_from.nil? + assert_equal Time.utc(today.year, today.month+1, today.day), new_todo.show_from + end + + def test_check_for_next_todo + login_as :admin_user + + recurring_todo_1 = RecurringTodo.find(5) + @todo = Todo.find_by_recurring_todo_id(1) + assert @todo.from_recurring_todo? + # rewire @todo to yearly recurring todo + @todo.recurring_todo_id = 5 + + # make todo due tomorrow and change recurring date also to tomorrow + @todo.due = Time.zone.now + 1.day + @todo.save + recurring_todo_1.every_other1 = @todo.due.day + recurring_todo_1.every_other2 = @todo.due.month + recurring_todo_1.save + + # mark todo complete + xhr :post, :toggle_check, :id => @todo.id, :_source_view => 'todo' + @todo.reload + assert @todo.completed? + + # check that there is no active todo + next_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'active'}) + assert next_todo.nil? + + # check for new deferred todo + next_todo = Todo.find(:first, :conditions => {:recurring_todo_id => recurring_todo_1.id, :state => 'deferred'}) + assert !next_todo.nil? + # check that the due date of the new todo is later than tomorrow + assert next_todo.due > @todo.due + end + end diff --git a/test/unit/context_test.rb b/test/unit/context_test.rb index 4952d8c6..121a7c29 100644 --- a/test/unit/context_test.rb +++ b/test/unit/context_test.rb @@ -53,7 +53,7 @@ class ContextTest < Test::Rails::TestCase end def test_delete_context_deletes_todos_within_it - assert_equal 6, @agenda.todos.count + assert_equal 7, @agenda.todos.count agenda_todo_ids = @agenda.todos.collect{|t| t.id } @agenda.destroy agenda_todo_ids.each do |todo_id| @@ -62,11 +62,11 @@ class ContextTest < Test::Rails::TestCase end def test_not_done_todos - assert_equal 5, @agenda.not_done_todos.size + assert_equal 6, @agenda.not_done_todos.size t = @agenda.not_done_todos[0] t.complete! t.save! - assert_equal 4, Context.find(@agenda.id).not_done_todos.size + assert_equal 5, Context.find(@agenda.id).not_done_todos.size end def test_done_todos diff --git a/test/unit/project_test.rb b/test/unit/project_test.rb index 966c8c6d..58f4f794 100644 --- a/test/unit/project_test.rb +++ b/test/unit/project_test.rb @@ -172,9 +172,9 @@ class ProjectTest < Test::Rails::TestCase def test_not_done_todo_count assert_equal 2, @timemachine.not_done_todo_count - assert_equal 3, @moremoney.not_done_todo_count + assert_equal 4, @moremoney.not_done_todo_count @moremoney.todos[0].complete! - assert_equal 2, @moremoney.not_done_todo_count + assert_equal 3, @moremoney.not_done_todo_count end def test_default_context_name diff --git a/test/unit/recurring_todo_test.rb b/test/unit/recurring_todo_test.rb index bd256eba..80106554 100644 --- a/test/unit/recurring_todo_test.rb +++ b/test/unit/recurring_todo_test.rb @@ -174,6 +174,9 @@ class RecurringTodoTest < Test::Rails::TestCase # same month, day after due_date = @yearly.get_due_date(@monday) # june 9th assert_equal Time.zone.local(2009,6,8), due_date # june 8th next year + # very overdue + due_date = @yearly.get_due_date(@monday+5.months-2.days) # november 7 + assert_equal Time.zone.local(2009,6,8), due_date # june 8th next year @yearly.recurrence_selector = 1 @yearly.every_other3 = 2 # second @@ -194,6 +197,15 @@ class RecurringTodoTest < Test::Rails::TestCase assert_equal due_date1, due_date2 end + def test_last_sunday_of_march + @yearly.recurrence_selector = 1 + @yearly.every_other2 = 3 # march + @yearly.every_other3 = 5 # last + @yearly.every_count = 0 # sunday + due_date = @yearly.get_due_date(Time.zone.local(2008,10,1)) # oct 1st + assert_equal Time.zone.local(2009,3,29), due_date # march 29th + end + def test_start_from_in_future # every_day should return start_day if it is in the future @every_day.start_from = @in_three_days