Made TodosController more RESTful and use it to fulfill all feeds, eliminating the need for the FeedController and it's helper and views. Also added an ATOM feed (not linked in the UI anywhere, just substitute .atom for .rss).

I also ran rcov on unit tests and added tests to improve test coverage, uncovering a couple of bugs along the way.

git-svn-id: http://www.rousette.org.uk/svn/tracks-repos/trunk@476 a4c988fc-2ded-0310-b66e-134b36920a42
This commit is contained in:
lukemelia 2007-03-18 00:38:05 +00:00
parent 84357b67d5
commit 106d5ee448
38 changed files with 1007 additions and 680 deletions

View file

@ -13,7 +13,7 @@ class ApplicationController < ActionController::Base
helper :application
include LoginSystem
layout 'standard'
before_filter :set_session_expiration
@ -21,6 +21,9 @@ class ApplicationController < ActionController::Base
after_filter :set_charset
include ActionView::Helpers::TextHelper
helper_method :format_date, :markdown
# By default, sets the charset to UTF-8 if it isn't already set
def set_charset
headers["Content-Type"] ||= "text/html; charset=UTF-8"
@ -78,13 +81,35 @@ class ApplicationController < ActionController::Base
end
def count_undone_todos(todos_parent)
if (todos_parent.is_a?(Project) && todos_parent.hidden?)
if todos_parent.nil?
count = 0
elsif (todos_parent.is_a?(Project) && todos_parent.hidden?)
count = eval "@project_project_hidden_todo_counts[#{todos_parent.id}]"
else
count = eval "@#{todos_parent.class.to_s.downcase}_not_done_counts[#{todos_parent.id}]"
end
count = 0 if count == nil
count
count || 0
end
# Convert a date object to the format specified
# in config/settings.yml
#
def format_date(date)
if date
date_format = @user.prefs.date_format
formatted_date = date.strftime("#{date_format}")
else
formatted_date = ''
end
formatted_date
end
# Uses RedCloth to transform text using either Textile or Markdown
# Need to require redcloth above
# RedCloth 3.0 or greater is needed to use Markdown, otherwise it only handles Textile
#
def markdown(text)
RedCloth.new(text).to_html
end
protected

View file

@ -1,133 +0,0 @@
# Produces an feeds of the next actions, both RSS and plain text
#
class FeedController < ApplicationController
helper :feed
layout nil
session :disabled => true # Prevents session control from interfering with feed
skip_before_filter :login_required
before_filter :check_token_against_user_word
before_filter :prepare_for_feed, :only => [:rss, :text, :ical]
before_filter :identify_contexts, :only => [:text, :ical]
# Build an RSS feed
def rss
headers["Content-Type"] = "text/xml; charset=utf-8"
end
# Builds a plain text page listing incomplete next actions,
# grouped by context (contexts are sorted by position, as on the home page).
# Showing notes doesn't make much sense here so they are omitted.
# Hidden contexts are also hidden in the text view
# You can use this with GeekTool to get your next actions
# on the desktop:
# curl [url from "TXT" link on todo/list]
#
def text
headers["Content-Type"] = "text/plain; charset=utf-8"
end
# Builds an iCal compatible export of incomplete todos
# so that each action forms a VTODO in your iCal calendar.
# Due dates are supported, and notes are included.
#
def ical
headers["Content-Type"] = "text/calendar"
end
protected
# Check whether the token in the URL matches the word in the User's table
def check_token_against_user_word
@user = User.find_by_login( params['login'] )
unless ( params['token'] == @user.word)
render :text => "Sorry, you don't have permission to view this page."
return false
end
end
def identify_contexts
if params.key?('context')
@contexts = [ @user.contexts.find_by_params(params) ]
else
@contexts = @user.contexts.find_all_by_hide(false, "position ASC")
end
end
def prepare_for_feed
condition_builder = FindConditionBuilder.new
options = Hash.new
if params.key?('done')
condition_builder.add 'todos.state = ?', 'completed'
else
condition_builder.add 'todos.state = ?', 'active'
end
if params.key?('limit')
options[:limit] = limit = params['limit']
@description = limit ? "Lists the last #{limit} incomplete next actions" : "Lists incomplete next actions"
end
@title = "Tracks - Next Actions"
@description = "Filter: "
if params.key?('due')
due_within = params['due'].to_i
due_within_when = @user.time + due_within.days
condition_builder.add('todos.due <= ?', due_within_when)
due_within_date_s = due_within_when.strftime("%Y-%m-%d")
@title << " due today" if (due_within == 0)
@title << " due within a week" if (due_within == 6)
@description << " with a due date #{due_within_date_s} or earlier"
end
if params.key?('done')
done_in_last = params['done'].to_i
condition_builder.add('todos.completed_at >= ?', @user.time - done_in_last.days)
@title << " actions completed"
@description << " in the last #{done_in_last.to_s} days"
end
if params.key?('context')
context = @user.contexts.find_by_params(params)
condition_builder.add('todos.context_id = ?', context.id)
@title << " in #{context.name}"
@description << " in context '#{context.name}'"
end
if params.key?('project')
project = @user.projects.find_by_params(params)
condition_builder.add('todos.project_id = ?', project.id)
@title << " for #{project.name}"
@description << " for project '#{project.name}'"
end
options[:conditions] = condition_builder.to_conditions
options[:order] = "todos.due IS NULL, todos.due ASC, todos.created_at ASC"
options[:include] = :project
@todos = @user.todos.find(:all, options )
end
class FindConditionBuilder
def initialize
@queries = Array.new
@params = Array.new
end
def add(query, param)
@queries << query
@params << param
end
def to_conditions
[@queries.join(' AND ')] + @params
end
end
end

View file

@ -138,14 +138,15 @@ class ProjectsController < ApplicationController
def render_rss_feed
lambda do
render_rss_feed_for @projects, :feed => Project.feed_options(@user),
:item => { :description => lambda { |p| p.summary(count_undone_todos_phrase(p)) } }
:item => { :title => :name, :description => lambda { |p| summary(p) } }
end
end
def render_atom_feed
lambda do
render_atom_feed_for @projects, :feed => Project.feed_options(@user),
:item => { :description => lambda { |p| p.summary(count_undone_todos_phrase(p)) },
:item => { :description => lambda { |p| summary(p) },
:title => :name,
:author => lambda { |p| nil } }
end
end
@ -181,4 +182,13 @@ class ProjectsController < ApplicationController
init_data_for_sidebar
end
def summary(project)
project_description = ''
project_description += sanitize(markdown( project.description )) unless project.description.blank?
project_description += "<p>#{count_undone_todos_phrase(p)}. "
project_description += "Project is #{project.state}."
project_description += "</p>"
project_description
end
end

View file

@ -3,33 +3,28 @@ class TodosController < ApplicationController
helper :todos
append_before_filter :init, :except => [ :destroy, :completed, :completed_archive, :check_deferred ]
skip_before_filter :login_required, :only => [:index]
prepend_before_filter :login_or_feed_token_required, :only => [:index]
session :off, :only => :index, :if => Proc.new { |req| is_feed_request(req) }
layout 'standard'
# Main method for listing tasks
# Set page title, and fill variables with contexts and done and not-done tasks
# Number of completed actions to show is determined by a setting in settings.yml
def index
@projects = @user.projects.find(:all, :include => [ :todos ])
@contexts = @user.contexts.find(:all, :include => [ :todos ])
@page_title = "TRACKS::List tasks"
# If you've set no_completed to zero, the completed items box
# isn't shown on the home page
max_completed = @user.prefs.show_number_completed
@done = @user.completed_todos.find(:all, :limit => max_completed, :include => [ :context, :project, :tags ]) unless max_completed == 0
@contexts_to_show = @contexts.reject {|x| x.hide? }
# Set count badge to number of not-done, not hidden context items
@count = @todos.reject { |x| !x.active? || x.context.hide? }.size
respond_to do |wants|
wants.html
wants.xml { render :action => 'list.rxml', :layout => false }
respond_to do |format|
format.html &render_todos_html
format.xml { render :action => 'list.rxml', :layout => false }
format.rss &render_rss_feed
format.atom &render_atom_feed
format.text &render_text_feed
format.ics &render_ical_feed
end
end
def create
@todo = @user.todos.build
p = params['request'] || params
@ -100,7 +95,7 @@ class TodosController < ApplicationController
#
def toggle_check
@todo = check_user_return_todo
@todo.toggle_completion()
@todo.toggle_completion!
@saved = @todo.save
respond_to do |format|
format.js do
@ -279,13 +274,89 @@ class TodosController < ApplicationController
init_data_for_sidebar
init_todos
end
def init_todos
# Exclude hidden projects from count on home page
@todos = @user.todos.find(:all, :conditions => ['todos.state = ? or todos.state = ?', 'active', 'complete'], :include => [ :project, :context, :tags ])
# Exclude hidden projects from the home page
@not_done_todos = @user.todos.find(:all, :conditions => ['todos.state = ?', 'active'], :order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC", :include => [ :project, :context, :tags ])
def with_feed_query_scope(&block)
unless TodosController.is_feed_request(request)
yield
return
end
condition_builder = FindConditionBuilder.new
options = Hash.new
if params.key?('done')
condition_builder.add 'todos.state = ?', 'completed'
else
condition_builder.add 'todos.state = ?', 'active'
end
@title = "Tracks - Next Actions"
@description = "Filter: "
if params.key?('due')
due_within = params['due'].to_i
due_within_when = @user.time + due_within.days
condition_builder.add('todos.due <= ?', due_within_when)
due_within_date_s = due_within_when.strftime("%Y-%m-%d")
@title << " due today" if (due_within == 0)
@title << " due within a week" if (due_within == 6)
@description << " with a due date #{due_within_date_s} or earlier"
end
if params.key?('done')
done_in_last = params['done'].to_i
condition_builder.add('todos.completed_at >= ?', @user.time - done_in_last.days)
@title << " actions completed"
@description << " in the last #{done_in_last.to_s} days"
end
Todo.with_scope :find => {:conditions => condition_builder.to_conditions} do
yield
end
end
def with_parent_resource_scope(&block)
if (params[:context_id])
context = @user.contexts.find_by_params(params)
Todo.with_scope :find => {:conditions => ['todos.context_id = ?', context.id]} do
yield
end
elsif (params[:project_id])
project = @user.projects.find_by_params(params)
Todo.with_scope :find => {:conditions => ['todos.project_id = ?', project.id]} do
yield
end
else
yield
end
end
def with_limit_scope(&block)
if params.key?('limit')
Todo.with_scope :find => {:limit => params['limit']} do
yield
end
#@description = limit ? "Lists the last #{limit} incomplete next actions" : "Lists incomplete next actions"
else
yield
end
end
def init_todos
with_feed_query_scope do
with_parent_resource_scope do
with_limit_scope do
# Exclude hidden projects from count on home page
@todos = @user.todos.find(:all, :conditions => ['todos.state = ? or todos.state = ?', 'active', 'complete'], :include => [ :project, :context, :tags ])
# Exclude hidden projects from the home page
@not_done_todos = @user.todos.find(:all, :conditions => ['todos.state = ?', 'active'], :order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC", :include => [ :project, :context, :tags ])
end
end
end
end
def determine_down_count
@ -323,5 +394,92 @@ class TodosController < ApplicationController
end
end
end
def render_todos_html
lambda do
@page_title = "TRACKS::List tasks"
# If you've set no_completed to zero, the completed items box
# isn't shown on the home page
max_completed = @user.prefs.show_number_completed
@done = @user.completed_todos.find(:all, :limit => max_completed, :include => [ :context, :project, :tags ]) unless max_completed == 0
# Set count badge to number of not-done, not hidden context items
@count = @todos.reject { |x| !x.active? || x.context.hide? }.size
render
end
end
def render_rss_feed
lambda do
render_rss_feed_for @todos, :feed => Todo.feed_options(@user),
:item => {
:title => :description,
:link => lambda { |t| context_url(t.context) },
:description => todo_feed_content
}
end
end
def todo_feed_content
lambda do |i|
item_notes = sanitize(markdown( i.notes )) if i.notes?
due = "<div>Due: #{format_date(i.due)}</div>\n" if i.due?
done = "<div>Completed: #{format_date(i.completed_at)}</div>\n" if i.completed?
context_link = "<a href=\"#{ context_url(i.context) }\">#{ i.context.name }</a>"
if i.project_id?
project_link = "<a href=\"#{ project_url(i.project) }\">#{ i.project.name }</a>"
else
project_link = "<em>none</em>"
end
"#{done||''}#{due||''}#{item_notes||''}\n<div>Project: #{project_link}</div>\n<div>Context: #{context_link}</div>"
end
end
def render_atom_feed
lambda do
render_atom_feed_for @todos, :feed => Todo.feed_options(@user),
:item => {
:title => :description,
:link => lambda { |t| context_url(t.context) },
:description => todo_feed_content,
:author => lambda { |p| nil }
}
end
end
def render_text_feed
lambda do
render :action => 'index_text', :layout => false, :content_type => Mime::TEXT
end
end
def render_ical_feed
lambda do
render :action => 'index_ical', :layout => false, :content_type => Mime::ICS
end
end
def self.is_feed_request(req)
['rss','atom','txt','ics'].include?(req.parameters[:format])
end
class FindConditionBuilder
def initialize
@queries = Array.new
@params = Array.new
end
def add(query, param)
@queries << query
@params << param
end
def to_conditions
[@queries.join(' AND ')] + @params
end
end
end

View file

@ -1,32 +1,10 @@
# The methods added to this helper will be available to all templates in the application.
module ApplicationHelper
# Convert a date object to the format specified
# in config/settings.yml
#
def format_date(date)
if date
date_format = @user.prefs.date_format
formatted_date = date.strftime("#{date_format}")
else
formatted_date = ''
end
formatted_date
end
def user_time
@user.time
end
# Uses RedCloth to transform text using either Textile or Markdown
# Need to require redcloth above
# RedCloth 3.0 or greater is needed to use Markdown, otherwise it only handles Textile
#
def markdown(text)
RedCloth.new(text).to_html
end
# Replicates the link_to method but also checks request.request_uri to find
# current page. If that matches the url, the link is marked
# id = "current"

View file

@ -1,43 +0,0 @@
module FeedHelper
# Build a nicely formatted text string for display
# Context forms the heading, then the items are
# indented underneath. If there is a due date
# and the item is in a project, these are also displayed
#
def build_text_page(list,context)
result_string = ""
list.each do |item|
if item.context_id == context.id
result_string << "\n" + context.name.upcase + ":\n" if result_string.empty?
if (item.completed?) && item.completed_at
result_string << " [Completed: " + format_date(item.completed_at) + "] "
end
if item.due
result_string << " [Due: " + format_date(item.due) + "] "
result_string << item.description + " "
else
result_string << " " + item.description + " "
end
if item.project_id
result_string << "(" + item.project.name + ")"
end
result_string << "\n"
end
end
return result_string
end
def format_ical_notes(notes)
split_notes = notes.split(/\n/)
joined_notes = split_notes.join("\\n")
end
def format_ical_uid(todo)
sprintf("%s%s%s%s", request.protocol, request.host, request.port_string, todo_url(todo))
end
end

View file

@ -1,24 +1,11 @@
module FeedlistHelper
def rss_feed_link(options = {})
image_tag = image_tag("feed-icon.png", :size => "16X16", :border => 0, :class => "rss-icon")
linkoptions = {:controller => 'feed', :action => 'rss', :login => "#{@user.login}", :token => "#{@user.word}"}
linkoptions.merge!(options)
link_to(image_tag, linkoptions, :title => "RSS feed")
end
def rss_formatted_link(options = {})
image_tag = image_tag("feed-icon.png", :size => "16X16", :border => 0, :class => "rss-icon")
linkoptions = { :token => @user.word, :format => 'rss' }
linkoptions.merge!(options)
link_to(image_tag, linkoptions, :title => "RSS feed")
end
def text_feed_link(options = {})
linkoptions = {:controller => 'feed', :action => 'text', :login => "#{@user.login}", :token => "#{@user.word}"}
linkoptions.merge!(options)
link_to('<span class="feed">TXT</span>', linkoptions, :title => "Plain text feed" )
end
def text_formatted_link(options = {})
linkoptions = { :token => @user.word, :format => 'txt' }
@ -26,12 +13,10 @@ module FeedlistHelper
link_to('<span class="feed">TXT</span>', linkoptions, :title => "Plain text feed" )
end
def ical_feed_link(options = {})
linkoptions = {:controller => 'feed', :action => 'ical', :login => "#{@user.login}", :token => "#{@user.word}"}
def ical_formatted_link(options = {})
linkoptions = { :token => @user.word, :format => 'ics' }
linkoptions.merge!(options)
link_to('<span class="feed">iCal</span>', linkoptions, :title => "iCal feed")
link_to('<span class="feed">iCal</span>', linkoptions, :title => "iCal feed" )
end
end

View file

@ -1,18 +1,3 @@
module LoginHelper
def render_errors(obj)
return "" unless obj
return "" unless request.post?
tag = String.new
unless obj.valid?
tag << %{<ul class="objerrors">}
obj.errors.each_full { |message| tag << %{<li>#{message}</li>} }
tag << %{</ul>}
end
tag
end
end

View file

@ -180,6 +180,11 @@ module TodosHelper
return array_or_string_for_javascript(['Create a new context']) if @contexts.empty?
array_or_string_for_javascript( @contexts.collect{|c| escape_javascript(c.name) } )
end
def format_ical_notes(notes)
split_notes = notes.split(/\n/)
joined_notes = split_notes.join("\\n")
end
private

View file

@ -19,7 +19,7 @@ class Context < ActiveRecord::Base
def self.feed_options(user)
{
:title => 'Tracks Contexts',
:description => "Lists all the contexts for #{user.display_name}."
:description => "Lists all the contexts for #{user.display_name}"
}
end
@ -37,7 +37,7 @@ class Context < ActiveRecord::Base
def summary(undone_todo_count)
s = "<p>#{undone_todo_count}. "
s += "Context is #{hidden? ? 'Hidden' : 'Active'}. "
s += "Context is #{hidden? ? 'Hidden' : 'Active'}."
s += "</p>"
s
end

View file

@ -40,50 +40,34 @@ class Project < ActiveRecord::Base
def self.feed_options(user)
{
:title => 'Tracks Projects',
:description => "Lists all the projects for #{user.display_name}."
:description => "Lists all the projects for #{user.display_name}"
}
end
def to_param
url_friendly_name
end
def description_present?
attribute_present?("description")
end
def linkurl_present?
attribute_present?("linkurl")
end
def title
name
end
def summary(undone_todo_count)
project_description = ''
project_description += sanitize(markdown( description )) if description_present?
project_description += "<p>#{undone_todo_count}. "
project_description += "Project is #{state}. "
project_description += "<a href=\"#{linkurl}\">#{linkurl}</a>" if linkurl_present?
project_description += "</p>"
project_description
end
def hide_todos
todos.each do |t|
t.hide! unless t.completed? || t.deferred?
t.save
unless t.completed? || t.deferred?
t.hide!
t.save
end
end
end
def unhide_todos
todos.each do |t|
t.unhide! if t.project_hidden?
t.save
if t.project_hidden?
t.unhide!
t.save
end
end
end
# would prefer to call this method state=(), but that causes an endless loop
# as a result of acts_as_state_machine calling state=() to update the attribute
def transition_to(candidate_state)
case candidate_state.to_sym
when current_state

View file

@ -1,5 +1,4 @@
class Todo < ActiveRecord::Base
require 'validations'
belongs_to :context, :order => 'name'
belongs_to :project
@ -45,12 +44,12 @@ class Todo < ActiveRecord::Base
validates_presence_of :context
def validate
if deferred? && !show_from.blank? && show_from < user.date
errors.add("Show From", "must be a date in the future.")
if !show_from.blank? && show_from < user.date
errors.add("show_from", "must be a date in the future")
end
end
def toggle_completion
def toggle_completion!
if completed?
activate!
else
@ -78,11 +77,18 @@ class Todo < ActiveRecord::Base
alias_method :original_set_initial_state, :set_initial_state
def set_initial_state
if show_from && (show_from > Time.now.utc.to_date)
if show_from && (show_from > user.date)
write_attribute self.class.state_column, 'deferred'
else
original_set_initial_state
end
end
def self.feed_options(user)
{
:title => 'Tracks Actions',
:description => "Actions for #{user.display_name}"
}
end
end

View file

@ -13,6 +13,8 @@ class User < ActiveRecord::Base
find_by_url_friendly_name(params['id'])
elsif params['context']
find_by_url_friendly_name(params['context'])
elsif params['context_id']
find_by_url_friendly_name(params['context_id'])
end
end
end
@ -28,6 +30,8 @@ class User < ActiveRecord::Base
find_by_url_friendly_name(params['id'])
elsif params['project']
find_by_url_friendly_name(params['project'])
elsif params['project_id']
find_by_url_friendly_name(params['project_id'])
end
end
def update_positions(project_ids)
@ -61,7 +65,7 @@ class User < ActiveRecord::Base
:conditions => [ 'state = ?', 'deferred' ],
:order => 'show_from ASC, todos.created_at DESC' do
def find_and_activate_ready
find(:all, :conditions => ['show_from <= ?', Time.now.utc.to_date ]).collect { |t| t.activate_and_save! }
find(:all, :conditions => ['show_from <= ?', Time.now.utc.to_date.to_time ]).collect { |t| t.activate_and_save! }
end
end
has_many :completed_todos,
@ -90,9 +94,14 @@ class User < ActiveRecord::Base
validates_confirmation_of :password
validates_length_of :login, :within => 3..80
validates_uniqueness_of :login, :on => :create
validates_inclusion_of :auth_type, :in => Tracks::Config.auth_schemes, :message=>"not a valid authentication type"
validates_presence_of :open_id_url, :if => Proc.new{|user| user.auth_type == 'open_id'}
def validate
unless Tracks::Config.auth_schemes.include?(auth_type)
errors.add("auth_type", "not a valid authentication type")
end
end
alias_method :prefs, :preference
def self.authenticate(login, pass)

View file

@ -0,0 +1,8 @@
<%
context = text_context
todos_in_context = todos.select { |t| t.context_id == context.id }
if todos_in_context.length > 0
%>
<%= context.name.upcase %>:
<% end -%>
<%= render :partial => "todos/text_todo", :collection => todos_in_context -%>

View file

@ -1,25 +0,0 @@
xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do
xml.channel do
xml.title(@title)
xml.link("http://#{request.host}:#{request.port}/todos/list")
xml.description(@description)
@todos.each do |i|
xml.item do
xml.title(i.description)
xml.link(context_url(i.context))
item_notes = sanitize(markdown( i.notes )) if i.notes?
due = "<div>Due: #{format_date(i.due)}</div>\n" if i.due?
toggle_link = link_to( "mark as done", {:only_path => false, :controller => "todos", :action => "toggle_check", :id => i.id})
done = "<div>#{toggle_link}</div>" unless i.completed?
done = "<div>Completed: #{format_date(i.completed_at)}</div>\n" if i.completed?
context_link = link_to( i.context.name, context_url(i.context) )
if i.project_id?
project_link = link_to(i.project.name, project_url(i.project) )
else
project_link = "<em>none</em>"
end
xml.description("#{done||''}#{due||''}#{item_notes||''}\n<div>Project: #{project_link}</div>\n<div>Context: #{context_link}</div>")
end
end
end
end

View file

@ -1,3 +0,0 @@
<% for @context in @contexts -%>
<%= build_text_page( @todos, @context ) -%>
<% end -%>

View file

@ -11,32 +11,32 @@
</div>
<ul>
<li>
<%= rss_feed_link({ :limit => 15 }) %>
<%= text_feed_link({ :limit => 15 }) %>
<%= ical_feed_link({ :limit => 15 }) %>
<%= rss_formatted_link({ :controller => 'todos', :action => 'index', :limit => 15 }) %>
<%= text_formatted_link({ :controller => 'todos', :action => 'index', :limit => 15 }) %>
<%= ical_formatted_link({ :controller => 'todos', :action => 'index', :limit => 15 }) %>
Last 15 actions
</li>
<li>
<%= rss_feed_link %>
<%= text_feed_link %>
<%= ical_feed_link %>
<%= rss_formatted_link( { :controller => 'todos', :action => 'index' } ) %>
<%= text_formatted_link( { :controller => 'todos', :action => 'index' } ) %>
<%= ical_formatted_link( { :controller => 'todos', :action => 'index' } ) %>
All actions
</li>
<li>
<%= rss_feed_link({ :due => 0 }) %>
<%= text_feed_link({ :due => 0 }) %>
<%= ical_feed_link({ :due => 0 }) %>
<%= rss_formatted_link({ :controller => 'todos', :action => 'index', :due => 0 }) %>
<%= text_formatted_link({ :controller => 'todos', :action => 'index', :due => 0 }) %>
<%= ical_formatted_link({ :controller => 'todos', :action => 'index', :due => 0 }) %>
Actions due today or earlier
</li>
<li>
<%= rss_feed_link({ :due => 6 }) %>
<%= text_feed_link({ :due => 6 }) %>
<%= ical_feed_link({ :due => 6 }) %>
<%= rss_formatted_link({ :controller => 'todos', :action => 'index', :due => 6 }) %>
<%= text_formatted_link({ :controller => 'todos', :action => 'index', :due => 6 }) %>
<%= ical_formatted_link({ :controller => 'todos', :action => 'index', :due => 6 }) %>
Actions due in 7 days or earlier
</li>
<li>
<%= rss_feed_link({ :done => 7 }) %>
<%= text_feed_link({ :done => 7 }) %>
<%= rss_formatted_link({ :controller => 'todos', :action => 'index', :done => 7 }) %>
<%= text_formatted_link({ :controller => 'todos', :action => 'index', :done => 7 }) %>
Actions completed in the last 7 days
</li>
<li>
@ -58,9 +58,9 @@
<ul>
<% for context in @contexts %>
<li>
<%= rss_feed_link({ :context => context }) %>
<%= text_feed_link({ :context => context }) %>
<%= ical_feed_link({ :context => context }) %>
<%= rss_formatted_link({ :controller=> 'todos', :action => 'index', :context_id => context.to_param }) %>
<%= text_formatted_link({ :controller=> 'todos', :action => 'index', :context_id => context.to_param }) %>
<%= ical_formatted_link({ :controller=> 'todos', :action => 'index', :context_id => context.to_param }) %>
Next actions in <strong><%=h context.name %></strong>
</li>
<% end %>
@ -70,9 +70,9 @@
<ul>
<% for project in @projects %>
<li>
<%= rss_feed_link({ :project => project }) %>
<%= text_feed_link({ :project => project }) %>
<%= ical_feed_link({ :project => project }) %>
<%= rss_formatted_link({ :controller=> 'todos', :action => 'index', :project_id => project.to_param }) %>
<%= text_formatted_link({ :controller=> 'todos', :action => 'index', :project_id => project.to_param }) %>
<%= ical_formatted_link({ :controller=> 'todos', :action => 'index', :project_id => project.to_param }) %>
Next actions for <strong><%=h project.name %></strong>
</li>
<% end %>

View file

@ -1,7 +1,6 @@
<% @projects.each do |p| -%>
<%= p.name.upcase %>
<%= p.description + "\n" if p.description_present? -%>
<%= p.description + "\n" unless p.description.blank? -%>
<%= count_undone_todos_phrase_text(p)%>. Project is <%= p.state %>.
<%= p.linkurl + "\n" if p.linkurl_present? -%>
<% end -%>

View file

@ -0,0 +1,19 @@
<%
todo = text_todo
result_string = ''
if (todo.completed?) && todo.completed_at
result_string << " [Completed: " + format_date(todo.completed_at) + "] "
end
if todo.due
result_string << " [Due: " + format_date(todo.due) + "] "
result_string << todo.description + " "
else
result_string << " " + todo.description + " "
end
unless todo.project.nil?
result_string << "(" + todo.project.name + ")"
end
-%>
<%= result_string %>

View file

@ -1,25 +1,25 @@
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//rousette.org.uk//Tracks 1.04//EN
CALSCALE:GREGORIAN
METHOD:PUBLISH
BEGIN:VTIMEZONE
TZID:<%= ENV['TZ'] || 'GMT' %>
LAST-MODIFIED:<%= Time.now.strftime("%Y%m%dT%H%M%SZ") %>
TZNAME:<%= ENV['TZ'] %>
END:VTIMEZONE
<% for @todo in @todos -%>
BEGIN:VTODO
DTSTAMP:<%= @todo.created_at.strftime("%Y%m%dT%H%M%SZ") %>
DTSTART;VALUE=DATE:<%= @todo.created_at.strftime("%Y%m%d") %>
SUMMARY:<%= @todo.description %>
UID:<%= format_ical_uid(@todo) %>
<% if @todo.notes? -%>
DESCRIPTION:<%= format_ical_notes(@todo.notes) %>
<% end -%>
<% if @todo.due -%>
DUE;VALUE=DATE:<%= @todo.due.strftime("%Y%m%d") %>
<% end -%>
END:VTODO
<% end -%>
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//rousette.org.uk//Tracks 1.04//EN
CALSCALE:GREGORIAN
METHOD:PUBLISH
BEGIN:VTIMEZONE
TZID:<%= ENV['TZ'] || 'GMT' %>
LAST-MODIFIED:<%= Time.now.strftime("%Y%m%dT%H%M%SZ") %>
TZNAME:<%= ENV['TZ'] %>
END:VTIMEZONE
<% for @todo in @todos -%>
BEGIN:VTODO
DTSTAMP:<%= @todo.created_at.strftime("%Y%m%dT%H%M%SZ") %>
DTSTART;VALUE=DATE:<%= @todo.created_at.strftime("%Y%m%d") %>
SUMMARY:<%= @todo.description %>
UID:<%= todo_url(@todo) %>
<% if @todo.notes? -%>
DESCRIPTION:<%= format_ical_notes(@todo.notes) %>
<% end -%>
<% if @todo.due -%>
DUE;VALUE=DATE:<%= @todo.due.strftime("%Y%m%d") %>
<% end -%>
END:VTODO
<% end -%>
END:VCALENDAR

View file

@ -0,0 +1 @@
<%= render :partial => "contexts/text_context", :collection => @contexts, :locals => { :todos => @todos } %>

View file

@ -17,7 +17,17 @@ ActionController::Routing::Routes.draw do |map|
users.signup 'signup', :action => "new"
end
# ToDo Routes
# Context Routes
map.resources :contexts, :collection => {:order => :post} do |contexts|
contexts.resources :todos
end
# Projects Routes
map.resources :projects, :collection => {:order => :post} do |projects|
projects.resources :todos
end
# ToDo Routes
map.resources :todos,
:member => {:toggle_check => :post},
:collection => {:check_deferred => :post}
@ -28,21 +38,12 @@ ActionController::Routing::Routes.draw do |map|
todos.done_archive 'done/archive', :action => "completed_archive"
todos.tag 'todos/tag/:name', :action => "tag"
end
# Context Routes
map.resources :contexts, :collection => {:order => :post}
map.connect 'context/:context/feed/:action/:login/:token', :controller => 'feed'
# Projects Routes
map.resources :projects, :collection => {:order => :post}
map.connect 'project/:project/feed/:action/:login/:token', :controller => 'feed'
# Notes Routes
map.resources :notes
# Feed Routes
map.connect 'feeds', :controller => 'feedlist', :action => 'index'
map.connect 'feed/:action/:login/:token', :controller => 'feed'
# Install the default route as the lowest priority.
map.connect ':controller/:action/:id'

View file

@ -1,136 +1,136 @@
# This file is autogenerated. Instead of editing this file, please use the
# migrations feature of ActiveRecord to incrementally modify your database, and
# then regenerate this schema definition.
ActiveRecord::Schema.define(:version => 30) do
create_table "contexts", :force => true do |t|
t.column "name", :string, :default => "", :null => false
t.column "hide", :integer, :limit => 4, :default => 0, :null => false
t.column "position", :integer, :default => 0, :null => false
t.column "user_id", :integer, :default => 0, :null => false
t.column "created_at", :datetime
t.column "updated_at", :datetime
end
add_index "contexts", ["user_id"], :name => "index_contexts_on_user_id"
add_index "contexts", ["user_id", "name"], :name => "index_contexts_on_user_id_and_name"
create_table "notes", :force => true do |t|
t.column "user_id", :integer, :default => 0, :null => false
t.column "project_id", :integer, :default => 0, :null => false
t.column "body", :text
t.column "created_at", :datetime
t.column "updated_at", :datetime
end
create_table "open_id_associations", :force => true do |t|
t.column "server_url", :binary
t.column "handle", :string
t.column "secret", :binary
t.column "issued", :integer
t.column "lifetime", :integer
t.column "assoc_type", :string
end
create_table "open_id_nonces", :force => true do |t|
t.column "nonce", :string
t.column "created", :integer
end
create_table "open_id_settings", :force => true do |t|
t.column "setting", :string
t.column "value", :binary
end
create_table "preferences", :force => true do |t|
t.column "user_id", :integer, :default => 0, :null => false
t.column "date_format", :string, :limit => 40, :default => "%d/%m/%Y", :null => false
t.column "week_starts", :integer, :default => 0, :null => false
t.column "show_number_completed", :integer, :default => 5, :null => false
t.column "staleness_starts", :integer, :default => 7, :null => false
t.column "show_completed_projects_in_sidebar", :boolean, :default => true, :null => false
t.column "show_hidden_contexts_in_sidebar", :boolean, :default => true, :null => false
t.column "due_style", :integer, :default => 0, :null => false
t.column "admin_email", :string, :default => "butshesagirl@rousette.org.uk", :null => false
t.column "refresh", :integer, :default => 0, :null => false
t.column "verbose_action_descriptors", :boolean, :default => false, :null => false
t.column "show_hidden_projects_in_sidebar", :boolean, :default => true, :null => false
t.column "time_zone", :string, :default => "London", :null => false
t.column "show_project_on_todo_done", :boolean, :default => false, :null => false
t.column "title_date_format", :string, :default => "%A, %d %B %Y", :null => false
end
add_index "preferences", ["user_id"], :name => "index_preferences_on_user_id"
create_table "projects", :force => true do |t|
t.column "name", :string, :default => "", :null => false
t.column "position", :integer, :default => 0, :null => false
t.column "user_id", :integer, :default => 0, :null => false
t.column "description", :text
t.column "state", :string, :limit => 20, :default => "active", :null => false
t.column "created_at", :datetime
t.column "updated_at", :datetime
end
add_index "projects", ["user_id"], :name => "index_projects_on_user_id"
add_index "projects", ["user_id", "name"], :name => "index_projects_on_user_id_and_name"
create_table "sessions", :force => true do |t|
t.column "session_id", :string
t.column "data", :text
t.column "updated_at", :datetime
end
add_index "sessions", ["session_id"], :name => "sessions_session_id_index"
create_table "taggings", :force => true do |t|
t.column "taggable_id", :integer
t.column "tag_id", :integer
t.column "taggable_type", :string
t.column "user_id", :integer
end
add_index "taggings", ["tag_id", "taggable_id", "taggable_type"], :name => "index_taggings_on_tag_id_and_taggable_id_and_taggable_type"
create_table "tags", :force => true do |t|
t.column "name", :string
t.column "created_at", :datetime
t.column "updated_at", :datetime
end
add_index "tags", ["name"], :name => "index_tags_on_name"
create_table "todos", :force => true do |t|
t.column "context_id", :integer, :default => 0, :null => false
t.column "description", :string, :limit => 100, :default => "", :null => false
t.column "notes", :text
t.column "created_at", :datetime
t.column "due", :date
t.column "completed_at", :datetime
t.column "project_id", :integer
t.column "user_id", :integer, :default => 0, :null => false
t.column "show_from", :date
t.column "state", :string, :limit => 20, :default => "immediate", :null => false
end
add_index "todos", ["user_id", "state"], :name => "index_todos_on_user_id_and_state"
add_index "todos", ["user_id", "project_id"], :name => "index_todos_on_user_id_and_project_id"
add_index "todos", ["project_id"], :name => "index_todos_on_project_id"
add_index "todos", ["context_id"], :name => "index_todos_on_context_id"
add_index "todos", ["user_id", "context_id"], :name => "index_todos_on_user_id_and_context_id"
create_table "users", :force => true do |t|
t.column "login", :string, :limit => 80
t.column "password", :string, :limit => 40
t.column "word", :string
t.column "is_admin", :integer, :limit => 4, :default => 0, :null => false
t.column "first_name", :string
t.column "last_name", :string
t.column "auth_type", :string, :default => "database", :null => false
t.column "open_id_url", :string
end
add_index "users", ["login"], :name => "index_users_on_login"
end
# This file is autogenerated. Instead of editing this file, please use the
# migrations feature of ActiveRecord to incrementally modify your database, and
# then regenerate this schema definition.
ActiveRecord::Schema.define(:version => 30) do
create_table "contexts", :force => true do |t|
t.column "name", :string, :default => "", :null => false
t.column "position", :integer, :null => false
t.column "hide", :boolean, :default => false
t.column "user_id", :integer, :default => 1
t.column "created_at", :datetime
t.column "updated_at", :datetime
end
add_index "contexts", ["user_id"], :name => "index_contexts_on_user_id"
add_index "contexts", ["user_id", "name"], :name => "index_contexts_on_user_id_and_name"
create_table "notes", :force => true do |t|
t.column "user_id", :integer, :null => false
t.column "project_id", :integer, :null => false
t.column "body", :text
t.column "created_at", :datetime
t.column "updated_at", :datetime
end
create_table "open_id_associations", :force => true do |t|
t.column "server_url", :binary
t.column "handle", :string
t.column "secret", :binary
t.column "issued", :integer
t.column "lifetime", :integer
t.column "assoc_type", :string
end
create_table "open_id_nonces", :force => true do |t|
t.column "nonce", :string
t.column "created", :integer
end
create_table "open_id_settings", :force => true do |t|
t.column "setting", :string
t.column "value", :binary
end
create_table "preferences", :force => true do |t|
t.column "user_id", :integer, :null => false
t.column "date_format", :string, :limit => 40, :default => "%d/%m/%Y", :null => false
t.column "week_starts", :integer, :default => 0, :null => false
t.column "show_number_completed", :integer, :default => 5, :null => false
t.column "staleness_starts", :integer, :default => 7, :null => false
t.column "show_completed_projects_in_sidebar", :boolean, :default => true, :null => false
t.column "show_hidden_contexts_in_sidebar", :boolean, :default => true, :null => false
t.column "due_style", :integer, :default => 0, :null => false
t.column "admin_email", :string, :default => "butshesagirl@rousette.org.uk", :null => false
t.column "refresh", :integer, :default => 0, :null => false
t.column "verbose_action_descriptors", :boolean, :default => false, :null => false
t.column "show_hidden_projects_in_sidebar", :boolean, :default => true, :null => false
t.column "time_zone", :string, :default => "London", :null => false
t.column "show_project_on_todo_done", :boolean, :default => false, :null => false
t.column "title_date_format", :string, :default => "%A, %d %B %Y", :null => false
end
add_index "preferences", ["user_id"], :name => "index_preferences_on_user_id"
create_table "projects", :force => true do |t|
t.column "name", :string, :default => "", :null => false
t.column "position", :integer, :null => false
t.column "user_id", :integer, :default => 1
t.column "description", :text
t.column "state", :string, :limit => 20, :default => "active", :null => false
t.column "created_at", :datetime
t.column "updated_at", :datetime
end
add_index "projects", ["user_id"], :name => "index_projects_on_user_id"
add_index "projects", ["user_id", "name"], :name => "index_projects_on_user_id_and_name"
create_table "sessions", :force => true do |t|
t.column "session_id", :string
t.column "data", :text
t.column "updated_at", :datetime
end
add_index "sessions", ["session_id"], :name => "index_sessions_on_session_id"
create_table "taggings", :force => true do |t|
t.column "taggable_id", :integer
t.column "tag_id", :integer
t.column "taggable_type", :string
t.column "user_id", :integer
end
add_index "taggings", ["tag_id", "taggable_id", "taggable_type"], :name => "index_taggings_on_tag_id_and_taggable_id_and_taggable_type"
create_table "tags", :force => true do |t|
t.column "name", :string
t.column "created_at", :datetime
t.column "updated_at", :datetime
end
add_index "tags", ["name"], :name => "index_tags_on_name"
create_table "todos", :force => true do |t|
t.column "context_id", :integer, :null => false
t.column "project_id", :integer
t.column "description", :string, :default => "", :null => false
t.column "notes", :text
t.column "created_at", :datetime
t.column "due", :date
t.column "completed_at", :datetime
t.column "user_id", :integer, :default => 1
t.column "show_from", :date
t.column "state", :string, :limit => 20, :default => "immediate", :null => false
end
add_index "todos", ["user_id", "state"], :name => "index_todos_on_user_id_and_state"
add_index "todos", ["user_id", "project_id"], :name => "index_todos_on_user_id_and_project_id"
add_index "todos", ["project_id"], :name => "index_todos_on_project_id"
add_index "todos", ["context_id"], :name => "index_todos_on_context_id"
add_index "todos", ["user_id", "context_id"], :name => "index_todos_on_user_id_and_context_id"
create_table "users", :force => true do |t|
t.column "login", :string, :limit => 80, :default => "", :null => false
t.column "password", :string, :limit => 40, :default => "", :null => false
t.column "word", :string
t.column "is_admin", :boolean, :default => false, :null => false
t.column "first_name", :string
t.column "last_name", :string
t.column "auth_type", :string, :default => "database", :null => false
t.column "open_id_url", :string
end
add_index "users", ["login"], :name => "index_users_on_login"
end

View file

@ -32,7 +32,7 @@ module LoginSystem
end
def login_or_feed_token_required
if ['rss', 'atom', 'txt'].include?(params[:format])
if ['rss', 'atom', 'txt', 'ics'].include?(params[:format])
if user = User.find_by_word(params[:token])
set_current_user(user)
return true

View file

@ -1,28 +0,0 @@
ActiveRecord::Validations::ClassMethods.class_eval do
# Custom validations
# Validating a date field parsed by Chronic. If Chronic cannot parse the
# date, it returns nil
# Adapted from Stuart Rackham's custom date validation
# http://www.bigbold.com/snippets/posts/show/1548
#
def validates_chronic_date(*attr_names)
configuration =
{ :message => 'is an invalid date. Here are some valid examples: feb 23, 23 feb 06, 6 feb 2006, 2006-02-23, tomorrow, today, 5 days (hence), 1 month hence, etc.)',
:on => :save,
}
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
# Don't let validates_each handle allow_nils, it checks the cast value.
allow_nil = configuration.delete(:allow_nil)
validates_each(attr_names, configuration) do |record, attr_name, value|
before_cast = record.send("#{attr_name}_before_type_cast")
next if allow_nil and (before_cast == '')
begin
date = Chronic.parse(before_cast.to_s).nil?
rescue
record.errors.add(attr_name, configuration[:message])
end
end
end
end

View file

@ -2,7 +2,7 @@
<%
def today
Time.now.utc.to_s(:db)
Time.now.utc.to_date.to_time.to_s(:db)
end
%>

View file

@ -2,19 +2,19 @@
<%
def today
Time.now.utc.to_s(:db)
Time.now.utc.to_date.to_time.to_s(:db)
end
def next_week
1.week.from_now.utc.to_s(:db)
1.week.from_now.to_date.to_time.utc.to_s(:db)
end
def last_week
1.week.ago.utc.to_s(:db)
1.week.ago.utc.to_date.to_time.to_s(:db)
end
def two_weeks_hence
2.week.from_now.utc.to_s(:db)
2.weeks.from_now.utc.to_date.to_time.to_s(:db)
end
%>
@ -136,7 +136,7 @@ end
state: completed
created_at: <%= today %>
due: <%= two_weeks_hence %>
completed_at: <%= today %>
completed_at: <%= last_week %>
user_id: 1
11:
@ -197,7 +197,7 @@ end
created_at: <%= today %>
due: ~
completed_at: ~
show_from: <%= next_week %>
show_from: <%= today %>
user_id: 1
16:

View file

@ -47,19 +47,23 @@ class ContextsControllerTest < TodoContainerControllerTestBase
#puts @response.body
assert_xml_select 'rss[version="2.0"]' do
assert_xml_select 'channel' do
assert_xml_select '>title', 'Tracks Contexts'
assert_xml_select '>description', "Lists all the contexts for #{users(:admin_user).display_name}."
assert_xml_select 'language', 'en-us'
assert_xml_select 'ttl', '40'
assert_select 'channel' do
assert_select '>title', 'Tracks Contexts'
assert_select '>description', "Lists all the contexts for #{users(:admin_user).display_name}"
assert_select 'language', 'en-us'
assert_select 'ttl', '40'
end
assert_xml_select 'item', 9 do
assert_xml_select 'title', /.+/
assert_xml_select 'description', /&lt;p&gt;\d+ actions. Context is (active|hidden). &lt;\/p&gt;/
%w(guid link).each do |node|
assert_xml_select node, /http:\/\/test.host\/contexts\/.+/
assert_select 'item', 9 do
assert_select 'title', /.+/
assert_select 'description' do
assert_select_encoded do
assert_select 'p', /\d+&nbsp;actions. Context is (Active|Hidden)./
end
end
assert_xml_select 'pubDate', /(#{contexts(:agenda).created_at.to_s(:rfc822)}|#{contexts(:library).created_at.to_s(:rfc822)})/
%w(guid link).each do |node|
assert_select node, /http:\/\/test.host\/contexts\/.+/
end
assert_select 'pubDate', /(#{contexts(:agenda).created_at.to_s(:rfc822)}|#{contexts(:library).created_at.to_s(:rfc822)})/
end
end
end
@ -89,12 +93,16 @@ class ContextsControllerTest < TodoContainerControllerTestBase
#puts @response.body
assert_xml_select 'feed[xmlns="http://www.w3.org/2005/Atom"]' do
assert_xml_select '>title', 'Tracks Contexts'
assert_xml_select '>subtitle', "Lists all the contexts for #{users(:admin_user).display_name}."
assert_xml_select 'entry', 3 do
assert_xml_select 'title', /.+/
assert_xml_select 'content[type="html"]', /&lt;p&gt;\d+ actions. Context is (active|hidden). &lt;\/p&gt;/
assert_xml_select 'published', /(#{contexts(:agenda).created_at.to_s(:rfc822)}|#{contexts(:library).created_at.to_s(:rfc822)})/
assert_select '>title', 'Tracks Contexts'
assert_select '>subtitle', "Lists all the contexts for #{users(:admin_user).display_name}"
assert_select 'entry', 9 do
assert_select 'title', /.+/
assert_select 'content[type="html"]' do
assert_select_encoded do
assert_select 'p', /\d+&nbsp;actions. Context is (Active|Hidden)./
end
end
assert_select 'published', /(#{contexts(:agenda).created_at.xmlschema}|#{contexts(:library).created_at.xmlschema})/
end
end
end

View file

@ -1,17 +0,0 @@
require File.dirname(__FILE__) + '/../test_helper'
require 'feed_controller'
# Re-raise errors caught by the controller.
class FeedController; def rescue_action(e) raise e end; end
class FeedControllerTest < Test::Unit::TestCase
def setup
@controller = FeedController.new
request, response = ActionController::TestRequest.new, ActionController::TestResponse.new
end
# Replace this with your real tests.
def test_truth
assert true
end
end

View file

@ -101,19 +101,23 @@ class ProjectsControllerTest < TodoContainerControllerTestBase
#puts @response.body
assert_xml_select 'rss[version="2.0"]' do
assert_xml_select 'channel' do
assert_xml_select '>title', 'Tracks Projects'
assert_xml_select '>description', "Lists all the projects for #{users(:admin_user).display_name}."
assert_xml_select 'language', 'en-us'
assert_xml_select 'ttl', '40'
assert_select 'channel' do
assert_select '>title', 'Tracks Projects'
assert_select '>description', "Lists all the projects for #{users(:admin_user).display_name}"
assert_select 'language', 'en-us'
assert_select 'ttl', '40'
end
assert_xml_select 'item', 3 do
assert_xml_select 'title', /.+/
assert_xml_select 'description', /&lt;p&gt;\d+ actions. Project is (active|hidden|completed). &lt;\/p&gt;/
%w(guid link).each do |node|
assert_xml_select node, /http:\/\/test.host\/projects\/.+/
assert_select 'item', 3 do
assert_select 'title', /.+/
assert_select 'description' do
assert_select_encoded do
assert_select 'p', /^\d+&nbsp;actions\. Project is (active|hidden|completed)\.$/
end
end
assert_xml_select 'pubDate', /(#{projects(:timemachine).updated_at.to_s(:rfc822)}|#{projects(:moremoney).updated_at.to_s(:rfc822)}})/
%w(guid link).each do |node|
assert_select node, /http:\/\/test.host\/projects\/.+/
end
assert_select 'pubDate', /(#{projects(:timemachine).updated_at.to_s(:rfc822)}|#{projects(:moremoney).updated_at.to_s(:rfc822)})/
end
end
end
@ -143,12 +147,16 @@ class ProjectsControllerTest < TodoContainerControllerTestBase
#puts @response.body
assert_xml_select 'feed[xmlns="http://www.w3.org/2005/Atom"]' do
assert_xml_select '>title', 'Tracks Projects'
assert_xml_select '>subtitle', "Lists all the projects for #{users(:admin_user).display_name}."
assert_xml_select 'entry', 3 do
assert_xml_select 'title', /.+/
assert_xml_select 'content[type="html"]', /&lt;p&gt;\d+ actions. Project is (active|hidden|completed). &lt;\/p&gt;/
assert_xml_select 'published', /(#{projects(:timemachine).updated_at.to_s(:rfc822)}|#{projects(:moremoney).updated_at.to_s(:rfc822)}})/
assert_select '>title', 'Tracks Projects'
assert_select '>subtitle', "Lists all the projects for #{users(:admin_user).display_name}"
assert_select 'entry', 3 do
assert_select 'title', /.+/
assert_select 'content[type="html"]' do
assert_select_encoded do
assert_select 'p', /\d+&nbsp;actions. Project is (active|hidden|completed)./
end
end
assert_select 'published', /(#{projects(:timemachine).updated_at.xmlschema}|#{projects(:moremoney).updated_at.xmlschema})/
end
end
end

View file

@ -123,6 +123,116 @@ class TodosControllerTest < Test::Unit::TestCase
assert_response :success
assert_equal 3, @tagged
end
def test_rss_feed_content
@request.session['user_id'] = users(:admin_user).id
get :index, { :format => "rss" }
assert_equal 'application/rss+xml; charset=utf-8', @response.headers["Content-Type"]
#puts @response.body
assert_xml_select 'rss[version="2.0"]' do
assert_select 'channel' do
assert_select '>title', 'Tracks Actions'
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 'title', /.+/
assert_select 'description', /.*/
%w(guid link).each do |node|
assert_select node, /http:\/\/test.host\/contexts\/.+/
end
assert_select 'pubDate', /(#{projects(:timemachine).updated_at.to_s(:rfc822)}|#{projects(:moremoney).updated_at.to_s(:rfc822)})/
end
end
end
end
def test_rss_feed_not_accessible_to_anonymous_user_without_token
@request.session['user_id'] = nil
get :index, { :format => "rss" }
assert_response 401
end
def test_rss_feed_not_accessible_to_anonymous_user_with_invalid_token
@request.session['user_id'] = nil
get :index, { :format => "rss", :token => 'foo' }
assert_response 401
end
def test_rss_feed_accessible_to_anonymous_user_with_valid_token
@request.session['user_id'] = nil
get :index, { :format => "rss", :token => users(:admin_user).word }
assert_response :ok
end
def test_atom_feed_content
@request.session['user_id'] = users(:admin_user).id
get :index, { :format => "atom" }
assert_equal 'application/atom+xml; charset=utf-8', @response.headers["Content-Type"]
#puts @response.body
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 'title', /.+/
assert_xml_select 'content[type="html"]', /.*/
assert_xml_select 'published', /(#{projects(:timemachine).updated_at.xmlschema}|#{projects(:moremoney).updated_at.xmlschema})/
end
end
end
def test_atom_feed_not_accessible_to_anonymous_user_without_token
@request.session['user_id'] = nil
get :index, { :format => "atom" }
assert_response 401
end
def test_atom_feed_not_accessible_to_anonymous_user_with_invalid_token
@request.session['user_id'] = nil
get :index, { :format => "atom", :token => 'foo' }
assert_response 401
end
def test_atom_feed_accessible_to_anonymous_user_with_valid_token
@request.session['user_id'] = nil
get :index, { :format => "atom", :token => users(:admin_user).word }
assert_response :ok
end
def test_text_feed_content
@request.session['user_id'] = users(:admin_user).id
get :index, { :format => "txt" }
assert_equal 'text/plain; charset=utf-8', @response.headers["Content-Type"]
assert !(/&nbsp;/.match(@response.body))
#puts @response.body
end
def test_text_feed_not_accessible_to_anonymous_user_without_token
@request.session['user_id'] = nil
get :index, { :format => "txt" }
assert_response 401
end
def test_text_feed_not_accessible_to_anonymous_user_with_invalid_token
@request.session['user_id'] = nil
get :index, { :format => "txt", :token => 'foo' }
assert_response 401
end
def test_text_feed_accessible_to_anonymous_user_with_valid_token
@request.session['user_id'] = nil
get :index, { :format => "txt", :token => users(:admin_user).word }
assert_response :ok
end
def test_ical_feed_content
@request.session['user_id'] = users(:admin_user).id
get :index, { :format => "ics" }
assert_equal 'text/calendar; charset=utf-8', @response.headers["Content-Type"]
assert !(/&nbsp;/.match(@response.body))
#puts @response.body
end
end

View file

@ -40,18 +40,32 @@ class ContextXmlApiTest < ActionController::IntegrationTest
def test_fails_with_too_long_name
invalid_with_long_name_postdata = "<request><context><name>foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoo arfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoo arfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfo barfoobarfoobarfoobarfoobarfoobarfoobar</name></context></request>"
authenticated_post_xml_to_context_create invalid_with_long_name_postdata
assert_response_and_body 404, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<errors>\n <error>Name context name must be less than 256 characters</error>\n</errors>\n"
assert_response 404
assert_xml_select 'errors' do
assert_select 'error', 1, 'Name context name must be less than 256 characters'
end
end
def test_fails_with_slash_in_name
authenticated_post_xml_to_context_create "<request><context><name>foo/bar</name></context></request>"
assert_response_and_body 404, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<errors>\n <error>Name cannot contain the slash ('/') character</error>\n</errors>\n"
assert_response 404
assert_xml_select 'errors' do
assert_select 'error', 1, 'Name cannot contain the slash (\'/\') character'
end
end
def test_creates_new_context
initial_count = Context.count
authenticated_post_xml_to_context_create
assert_response_and_body_matches 200, %r|^<\?xml version="1.0" encoding="UTF-8"\?>\n<context>\n <created-at type=\"datetime\">\d{4}+-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z</created-at>\n <hide type="integer">0</hide>\n <id type="integer">\d+</id>\n <name>#{@@context_name}</name>\n <position type="integer">3</position>\n <updated-at type=\"datetime\">\d{4}+-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z</updated-at>\n</context>\n$|
assert_response 200
assert_xml_select 'context' do
assert_select 'created-at', /\d{4}+-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/
assert_select 'hide', 'false'
assert_select 'id', /\d+/
assert_select 'name', @@context_name
assert_select 'position', '3'
assert_select 'updated-at', /\d{4}+-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/
end
assert_equal initial_count + 1, Context.count
context1 = Context.find_by_name(@@context_name)
assert_not_nil context1, "expected context '#{@@context_name}' to be created"

View file

@ -1,7 +1,7 @@
require File.dirname(__FILE__) + '/../test_helper'
require 'projects_controller'
require 'contexts_controller'
require 'feed_controller'
require 'todos_controller'
# Re-raise errors caught by the controller.
class ProjectsController; def rescue_action(e) raise e end; end
@ -16,59 +16,87 @@ class FeedSmokeTest < ActionController::IntegrationTest
end
def test_last_15_actions_rss
assert_success "/feed/rss/admin/#{ users(:admin_user).word }?limit=15"
assert_success "/todos.rss?token=#{ users(:admin_user).word }&limit=15"
end
def test_last_15_actions_atom
assert_success "/todos.atom?token=#{ users(:admin_user).word }&limit=15"
end
def test_last_15_actions_txt
assert_success "/feed/text/admin/#{ users(:admin_user).word }?limit=15"
assert_success "/todos.txt?token=#{ users(:admin_user).word }&limit=15"
end
def test_last_15_actions_ical
assert_success "/feed/ical/admin/#{ users(:admin_user).word }?limit=15"
assert_success "/todos.ics?token=#{ users(:admin_user).word }&limit=15"
end
def test_all_actions_rss
assert_success "/feed/rss/admin/#{ users(:admin_user).word }"
assert_success "/todos.rss?token=#{ users(:admin_user).word }"
end
def test_all_actions_txt
assert_success "/feed/text/admin/#{ users(:admin_user).word }"
assert_success "/todos.txt?token=#{ users(:admin_user).word }"
end
def test_all_actions_ical
assert_success "/feed/ical/admin/#{ users(:admin_user).word }"
assert_success "/todos.ics?token=#{ users(:admin_user).word }"
end
def test_all_actions_in_context_rss
assert_success "/contexts/agenda/todos.rss?token=#{ users(:admin_user).word }"
end
def test_all_actions_in_context_txt
assert_success "/contexts/agenda/todos.txt?token=#{ users(:admin_user).word }"
end
def test_all_actions_in_context_ical
assert_success "/contexts/agenda/todos.ics?token=#{ users(:admin_user).word }"
end
def test_all_actions_in_project_rss
assert_success "/projects/Build_a_working_time_machine/todos.rss?token=#{ users(:admin_user).word }"
end
def test_all_actions_in_project_txt
assert_success "/projects/Build_a_working_time_machine/todos.txt?token=#{ users(:admin_user).word }"
end
def test_all_actions_in_project_ical
assert_success "/projects/Build_a_working_time_machine/todos.ics?token=#{ users(:admin_user).word }"
end
def test_all_actions_due_today_or_earlier_rss
assert_success "/feed/rss/admin/#{ users(:admin_user).word }?due=0"
assert_success "/todos.rss?token=#{ users(:admin_user).word }&due=0"
end
def test_all_actions_due_today_or_earlier_txt
assert_success "/feed/text/admin/#{ users(:admin_user).word }?due=0"
assert_success "/todos.txt?token=#{ users(:admin_user).word }&due=0"
end
def test_all_actions_due_today_or_earlier_ical
assert_success "/feed/ical/admin/#{ users(:admin_user).word }?due=0"
assert_success "/todos.ics?token=#{ users(:admin_user).word }&due=0"
end
def test_all_actions_due_in_7_days_or_earlier_rss
assert_success "/feed/rss/admin/#{ users(:admin_user).word }?due=6"
assert_success "/todos.rss?token=#{ users(:admin_user).word }&due=6"
end
def test_all_actions_due_in_7_days_or_earlier_txt
assert_success "/feed/text/admin/#{ users(:admin_user).word }?due=6"
assert_success "/todos.txt?token=#{ users(:admin_user).word }&due=6"
end
def test_all_actions_due_in_7_days_or_earlier_ical
assert_success "/feed/ical/admin/#{ users(:admin_user).word }?due=6"
assert_success "/todos.ics?token=#{ users(:admin_user).word }&due=6"
end
def test_all_actions_completed_in_last_7_days_rss
assert_success "/feed/rss/admin/#{ users(:admin_user).word }?done=7"
assert_success "/todos.rss?token=#{ users(:admin_user).word }&done=7"
end
def test_all_actions_completed_in_last_7_days_txt
assert_success "/feed/text/admin/#{ users(:admin_user).word }?done=7"
assert_success "/todos.txt?token=#{ users(:admin_user).word }&done=7"
end
def test_all_contexts_rss

View file

@ -35,9 +35,9 @@ class Test::Unit::TestCase
@xml_document ||= HTML::Document.new(@response.body, false, true)
end
def assert_xml_select(*args)
def assert_xml_select(*args, &block)
@html_document = xml_document
assert_select(*args)
assert_select(*args, &block)
end
def next_week

View file

@ -112,5 +112,24 @@ class ContextTest < Test::Unit::TestCase
def test_title_reader_returns_name
assert_equal @agenda.name, @agenda.title
end
def test_feed_options
opts = Context.feed_options(users(:admin_user))
assert_equal 'Tracks Contexts', opts[:title], 'Unexpected value for :title key of feed_options'
assert_equal 'Lists all the contexts for Admin Schmadmin', opts[:description], 'Unexpected value for :description key of feed_options'
end
def test_hidden_attr_reader
assert !@agenda.hidden?
@agenda.hide = true
assert @agenda.hidden?
end
def test_summary
undone_todo_count = '5 actions'
assert_equal "<p>#{undone_todo_count}. Context is Active.</p>", @agenda.summary(undone_todo_count)
@agenda.hide = true
assert_equal "<p>#{undone_todo_count}. Context is Hidden.</p>", @agenda.summary(undone_todo_count)
end
end

View file

@ -142,9 +142,55 @@ class ProjectTest < Test::Unit::TestCase
def test_to_param_returns_url_friendly_name
assert_equal 'Build_a_working_time_machine', @timemachine.to_param
end
def test_title_reader_returns_name
assert_equal @timemachine.name, @timemachine.title
def test_null_object
p = Project.null_object
assert !p.hidden?
assert p.nil?
assert_nil p.id
end
def test_feed_options
opts = Project.feed_options(users(:admin_user))
assert_equal 'Tracks Projects', opts[:title], 'Unexpected value for :title key of feed_options'
assert_equal 'Lists all the projects for Admin Schmadmin', opts[:description], 'Unexpected value for :description key of feed_options'
end
def test_transition_to_another_state
assert_equal :active, @timemachine.current_state
@timemachine.transition_to(:hidden)
assert_equal :hidden, @timemachine.current_state
@timemachine.transition_to(:completed)
assert_equal :completed, @timemachine.current_state
@timemachine.transition_to(:active)
assert_equal :active, @timemachine.current_state
end
def test_transition_to_same_state
assert_equal :active, @timemachine.current_state
@timemachine.transition_to(:active)
assert_equal :active, @timemachine.current_state
end
def test_deferred_todo_count
assert_equal 1, @timemachine.deferred_todo_count
assert_equal 0, @moremoney.deferred_todo_count
@moremoney.todos[0].show_from = next_week
assert_equal 1, @moremoney.deferred_todo_count
end
def test_done_todo_count
assert_equal 0, @timemachine.done_todo_count
assert_equal 0, @moremoney.done_todo_count
@moremoney.todos[0].complete!
assert_equal 1, @moremoney.done_todo_count
end
def test_not_done_todo_count
assert_equal 2, @timemachine.not_done_todo_count
assert_equal 3, @moremoney.not_done_todo_count
@moremoney.todos[0].complete!
assert_equal 2, @moremoney.not_done_todo_count
end
end

View file

@ -2,7 +2,7 @@ require File.dirname(__FILE__) + '/../test_helper'
require 'date'
class TodoTest < Test::Unit::TestCase
fixtures :todos, :users, :contexts
fixtures :todos, :users, :contexts, :preferences
def setup
@not_completed1 = Todo.find(1).reload
@ -19,8 +19,8 @@ class TodoTest < Test::Unit::TestCase
assert_equal "Call Bill Gates to find out how much he makes per day", @not_completed1.description
assert_nil @not_completed1.notes
assert @not_completed1.completed? == false
assert_equal 1.week.ago.utc.strftime("%Y-%m-%d %H:%M"), @not_completed1.created_at.strftime("%Y-%m-%d %H:%M")
assert_equal 2.week.from_now.utc.strftime("%Y-%m-%d"), @not_completed1.due.strftime("%Y-%m-%d")
assert_equal 1.week.ago.utc.to_date.to_time.strftime("%Y-%m-%d %H:%M"), @not_completed1.created_at.strftime("%Y-%m-%d %H:%M")
assert_equal 2.week.from_now.utc.to_date.to_time.strftime("%Y-%m-%d"), @not_completed1.due.strftime("%Y-%m-%d")
assert_nil @not_completed1.completed_at
assert_equal 1, @not_completed1.user_id
end
@ -56,6 +56,16 @@ class TodoTest < Test::Unit::TestCase
assert_equal 1, @not_completed2.errors.count
assert_equal "is too long (maximum is 60000 characters)", @not_completed2.errors.on(:notes)
end
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
# 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)
end
def test_defer_an_existing_todo
@not_completed2
@ -74,4 +84,58 @@ class TodoTest < Test::Unit::TestCase
assert item.save, "should have saved successfully" + item.errors.to_xml
assert_equal :deferred, item.current_state
end
def test_feed_options
opts = Todo.feed_options(users(:admin_user))
assert_equal 'Tracks Actions', opts[:title], 'Unexpected value for :title key of feed_options'
assert_equal 'Actions for Admin Schmadmin', opts[:description], 'Unexpected value for :description key of feed_options'
end
def test_toggle_completion
t = @not_completed1
assert_equal :active, t.current_state
t.toggle_completion!
assert_equal :completed, t.current_state
t.toggle_completion!
assert_equal :active, t.current_state
end
def test_activate_and_save
t = @not_completed1
t.show_from = 1.week.from_now.to_date
t.save!
assert t.deferred?
t.reload
t.activate_and_save!
assert t.active?
t.reload
assert t.active?
end
def test_project_returns_null_object_when_nil
t = @not_completed1
assert !t.project.is_a?(NullProject)
t.project = nil
assert t.project.is_a?(NullProject)
end
def test_initial_state_defaults_to_active
t = Todo.new
t.description = 'foo'
t.context_id = 1
t.save!
t.reload
assert_equal :active, t.current_state
end
def test_initial_state_is_deferred_when_show_from_in_future
t = Todo.new
t.description = 'foo'
t.context_id = 1
t.show_from = 1.week.from_now.to_date
t.save!
t.reload
assert_equal :deferred, t.current_state
end
end

View file

@ -1,11 +1,28 @@
require File.dirname(__FILE__) + '/../test_helper'
module Tracks
class Config
def self.auth_schemes
['database', 'ldap']
end
end
end
class SimpleLdapAuthenticator
cattr_accessor :fake_success
def self.valid?(login, pass)
fake_success
end
end
class UserTest < Test::Unit::TestCase
fixtures :users, :preferences, :projects, :todos
fixtures :users, :preferences, :projects, :contexts, :todos
def setup
assert_equal "test", ENV['RAILS_ENV']
assert_equal "change-me", Tracks::Config.salt
assert_equal ['database', 'ldap'], Tracks::Config.auth_schemes
@admin_user = User.find(1)
@other_user = User.find(2)
end
@ -28,15 +45,15 @@ class UserTest < Test::Unit::TestCase
assert_equal "jane", @other_user.login
assert_equal "#{Digest::SHA1.hexdigest("#{Tracks::Config.salt}--sesame--")}", @other_user.password
assert_not_nil @other_user.word
assert @other_user.is_admin == false || @other_user.is_admin == 0
assert @other_user.is_admin == false || @other_user.is_admin == 0
end
# ============================================
# Validations
# ============================================
# Test a password shorter than 5 characters
#
#
def test_validate_short_password
assert_equal "#{Digest::SHA1.hexdigest("#{Tracks::Config.salt}--sesame--")}", @other_user.password
@other_user.password = "four"
@ -46,25 +63,25 @@ class UserTest < Test::Unit::TestCase
end
# Test a password longer than 40 characters
#
#
def test_validate_long_password
assert_equal "#{Digest::SHA1.hexdigest("#{Tracks::Config.salt}--sesame--")}", @other_user.password
@other_user.password = generate_random_string(41)
assert !@other_user.save
assert_equal 1, @other_user.errors.count
assert_equal "is too long (maximum is 40 characters)", @other_user.errors.on(:password)
end
end
# Test that correct length password is valid
#
#
def test_validate_correct_length_password
assert_equal "#{Digest::SHA1.hexdigest("#{Tracks::Config.salt}--sesame--")}", @other_user.password
@other_user.password = generate_random_string(6)
assert @other_user.save
end
# Test a missing password
#
#
def test_validate_missing_password
assert_equal 2, @other_user.id
@other_user.password = ""
@ -72,9 +89,9 @@ class UserTest < Test::Unit::TestCase
assert_equal 2, @other_user.errors.count
assert_equal ["is too short (minimum is 5 characters)", "can't be blank"], @other_user.errors.on(:password)
end
# Test a login shorter than 3 characters
#
#
def test_validate_short_login
assert_equal "jane", @other_user.login
@other_user.login = "ba"
@ -82,27 +99,27 @@ class UserTest < Test::Unit::TestCase
assert_equal 1, @other_user.errors.count
assert_equal "is too short (minimum is 3 characters)", @other_user.errors.on(:login)
end
# Test a login longer than 80 characters
#
#
def test_validate_long_login
assert_equal "jane", @other_user.login
@other_user.login = generate_random_string(81)
assert !@other_user.save
assert_equal 1, @other_user.errors.count
assert_equal "is too long (maximum is 80 characters)", @other_user.errors.on(:login)
end
end
# Test that correct length login is valid
#
#
def test_validate_correct_length_login
assert_equal "jane", @other_user.login
@other_user.login = generate_random_string(6)
assert @other_user.save
end
# Test a missing login
#
#
def test_validate_missing_login
assert_equal 2, @other_user.id
@other_user.login = ""
@ -110,39 +127,39 @@ class UserTest < Test::Unit::TestCase
assert_equal 2, @other_user.errors.count
assert_equal ["is too short (minimum is 3 characters)", "can't be blank"], @other_user.errors.on(:login)
end
def test_display_name_with_first_and_last_name_set
@other_user.first_name = "Jane"
@other_user.last_name = "Doe"
assert_equal "Jane Doe", @other_user.display_name
end
def test_display_name_with_first_name_set
@other_user.first_name = "Jane"
@other_user.last_name = nil
assert_equal "Jane", @other_user.display_name
end
def test_display_name_with_last_name_set
@other_user.first_name = nil
@other_user.last_name = "Doe"
assert_equal "Doe", @other_user.display_name
end
def test_display_name_with_neither_first_nor_last_name_set
@other_user.first_name = nil
@other_user.last_name = nil
assert_equal @other_user.login, @other_user.display_name
end
def test_prefs_is_short_for_preference
assert_equal @admin_user.preference, @admin_user.prefs
end
def test_to_param_returns_login
assert_equal @admin_user.login, @admin_user.to_param
end
def test_change_password
assert_not_nil User.authenticate(@admin_user.login, "abracadabra")
@admin_user.change_password("foobar", "foobar")
@ -152,28 +169,117 @@ class UserTest < Test::Unit::TestCase
end
def test_projects_next_project
moremoney = projects(:moremoney)
next_project = @admin_user.projects.next_from(moremoney)
assert_equal projects(:gardenclean), next_project
moremoney = projects(:moremoney)
next_project = @admin_user.projects.next_from(moremoney)
assert_equal projects(:gardenclean), next_project
end
def test_projects_previous_project
moremoney = projects(:moremoney)
previous_project = @admin_user.projects.previous_from(moremoney)
assert_equal projects(:timemachine), previous_project
moremoney = projects(:moremoney)
previous_project = @admin_user.projects.previous_from(moremoney)
assert_equal projects(:timemachine), previous_project
end
def test_projects_next_project_nil
gardenclean = projects(:gardenclean)
next_project = @admin_user.projects.next_from(gardenclean)
assert_nil next_project
gardenclean = projects(:gardenclean)
next_project = @admin_user.projects.next_from(gardenclean)
assert_nil next_project
end
def test_projects_previous_project_nil
timemachine = projects(:timemachine)
previous_project = @admin_user.projects.previous_from(timemachine)
assert_nil previous_project
timemachine = projects(:timemachine)
previous_project = @admin_user.projects.previous_from(timemachine)
assert_nil previous_project
end
def test_no_users_yet
assert !User.no_users_yet?
User.delete_all
assert User.no_users_yet?
end
def test_crypt_word_updates_word
old_word = @admin_user.word
@admin_user.crypt_word
assert_not_equal old_word, @admin_user.word
end
def test_find_admin
assert_equal @admin_user, User.find_admin
end
def test_validates_auth_type
@other_user.auth_type = 'dnacheck'
assert !@other_user.save
assert_equal 1, @other_user.errors.count
assert_equal "not a valid authentication type", @other_user.errors.on(:auth_type)
end
def test_authenticate_can_use_ldap
u = @other_user
u.auth_type = 'ldap'
u.save!
SimpleLdapAuthenticator.fake_success = false
assert_nil User.authenticate(u.login, 'foobar')
SimpleLdapAuthenticator.fake_success = true
assert_equal @other_user, User.authenticate(u.login, 'foobar')
end
def test_find_context_by_params
u = @admin_user
c = u.contexts.find_by_params('url_friendly_name' => 'agenda')
assert_equal contexts(:agenda), c
c = u.contexts.find_by_params('id' => 'agenda')
assert_equal contexts(:agenda), c
c = u.contexts.find_by_params('id' => '1')
assert_equal contexts(:agenda), c
c = u.contexts.find_by_params('context' => 'agenda')
assert_equal contexts(:agenda), c
c = u.contexts.find_by_params('context_id' => 'agenda')
assert_equal contexts(:agenda), c
end
def test_find_project_by_params
u = @admin_user
p = u.projects.find_by_params('url_friendly_name' => 'Build_a_working_time_machine')
assert_equal projects(:timemachine), p
p = u.projects.find_by_params('id' => 'Build_a_working_time_machine')
assert_equal projects(:timemachine), p
p = u.projects.find_by_params('id' => '1')
assert_equal projects(:timemachine), p
p = u.projects.find_by_params('project' => 'Build_a_working_time_machine')
assert_equal projects(:timemachine), p
p = u.projects.find_by_params('project_id' => 'Build_a_working_time_machine')
assert_equal projects(:timemachine), p
end
def test_update_project_positions
assert_equal 1, Project.find(1).position
assert_equal 2, Project.find(2).position
assert_equal 3, Project.find(3).position
@admin_user.projects.update_positions([2,1,3])
assert_equal 2, Project.find(1).position
assert_equal 1, Project.find(2).position
assert_equal 3, Project.find(3).position
end
def test_find_and_activate_deferred_todos_that_are_ready
assert_equal 1, @admin_user.deferred_todos.count
@admin_user.deferred_todos.find_and_activate_ready
@admin_user.deferred_todos.reload
assert_equal 0, @admin_user.deferred_todos.count
end
def test_completed_todos_completed_within
todos = @admin_user.completed_todos.completed_within(@admin_user.time - 1.day)
assert_equal 3, todos.length
end
def test_completed_todos_complete_more_than
todos = @admin_user.completed_todos.completed_more_than(@admin_user.time - 1.day)
assert_equal 1, todos.length
end
end