Monthly Archive for August, 2009

Specify a relative time in Cucumber

Prepping for working on a feature at $work that involves limiting documents by relative time, I hashed out the following steps. Posting here in case they prove useful for anyone.

I want to be able to specify an arbitrary time, in english, relative to the current time, in my Cucumber steps. For example,

Scenario: Only posts from the last two months are shown
  Given the following posts:
    | title           | posted_at           |
    | recent post     | 2 days ago          |
    | recent post 2   | yesterday           |
    | older post      | 2 months ago        |
    | really old post | 2 months, 1 day ago |
    | tomorrow's post | 1 day from now      |
  When I go to the posts page
  Then I should see 3 posts

The desire, there, is to be able to specify a date relative to the current time, in plain english. The tricky bit is being able to chain them together, as in 1 year, 3 months, 2 days ago

I got this worked out by stringing a few regular expressions together in my step definition helper. It depends on the 2.days.ago type dsl in ActiveSupport.

Given /^the following posts?:?/ do |table|
  table.map_column!('posted_at'){|date| interpret_time(date) }
  table.hashes.each{|hash| Factory :post, hash}
end
def interpret_time(time)
  return nil if time.blank?
  return time if time.kind_of?(Time) or time.kind_of?(Date)
  named_time(time) || time_by_regex(time) || Time.parse(time)
end
 
def named_time(time)
  case time.downcase
  when "today" : Date.today
  when "now" : Time.now
  # etc ...
  else nil
  end
end
 
def time_by_regex(time)
  directional_regex = /(ago|from now)/i
  time_regex = /(\d+) (minute|hour|day|week|month|year)s?/i
 
  direction = time.scan(directional_regex).flatten
  shifts = time.scan(time_regex)
 
  return nil if direction.empty? or shifts.empty?
  forward = direction.first == "from now"
 
  result = Time.now
  shifts.each do |count, unit|
    #depends on the 1.week type DSL for time measurements
    adjust = count.to_i.send(unit)
    # move time in correct direction
    result = forward ? (result + adjust) : (result - adjust)
  end
 
  result
end