Posts Tagged ‘sinatra’

Ugly hack of the week: matching text in a div

Thursday, March 19th, 2009

Edit 10/27/2010: As some folks pointed out in the comments, there are MUCH better ways to do this.  Like using webrat’s have_selector matcher.  This was a nice exercise in writing a simple parser for me though!

Edit 3/20/2009: Updated code o work for other tags besides div!

Often in my cucumber scenarios I check for some text on a page:

When I visit /
Then I should see "booga booga"

The trouble is, sometimes I care not just whether the text is there, but where it is. If I’m looking at a report about our chickens, I’d like to make sure that some text is showing up with a specific one:

When I visit /chickens
Then in "oscar" I should see "color: yellow"

There was an interesting discussion (here and here) on the RSpec list this month about how to do something like that. Apparently you can do with with the XPath matchers. And Phlip write a very nice RSpec matcher for the purpose.

But I’m using Sinatra with Cucumber and I haven’t really around to setting up RSpec with it, let alone figuring out how to configure a custom matcher. I started to set that stuff up, but it started to feel like yak shaving, so I just rolled my own, including a very rudimentary parser:

# features/step_definitions/my_webrat_steps.rb
 
Then /^in "(.*)" I should see "(.*)"$/ do |id, text|
  body = get_inner_html(id, response.body)
  body.should =~ /#{text}/i
end
 
Then /^in "(.*)" I should not see "(.*)"$/ do |id, text|
  body = get_inner_html(id, response.body)
  body.should_not =~ /#{text}/i
end
 
def get_inner_html(id, body)
  start = false
  nested = 0
  body = body.gsub(/\n/, "")
  buffer = Buffer.new(body)
  while buffer.next_one do
    if start == false
      tag = "<[a-z]* id=\"#{id}\"[^>]*>"
      if buffer.ends_with?(tag)
        match = buffer.first_half.match(/<([a-z]*) id="#{id}"[^>]*>$/)
        full_tag = match[0]
        entity = match[1]
        start = buffer.position+1
      end
    else
      if buffer.ends_with?("<" << entity)
        nested += 1
      end
      if buffer.ends_with?("<!--" << entity << "-->")
        if nested == 0
          return body[start, buffer.position-start-entity.length-2]
        else
          nested -= 1
        end
      end
    end
  end
end
 
class Buffer
  def initialize(str)
    @str = str
    @pointer = -1
  end
 
  def next_one
    @pointer += 1
  end
 
  def first_half
    @str[0,@pointer+1]
  end
 
  def ends_with?(end_str)
    end_str &lt;&lt;= "$"
    first_half.match(end_str)
  end
 
  def position
    @pointer
  end
end

It seems to work ok!