Autonomous Machine

Simple Rspec Markup Matcher

It seems like every project I've worked on recently involves some kind of markup processing. Here's what I've been using to test my markup processing:

module Spec
  module Matchers
    module Markup
      class MatchMarkup
        def initialize(expected)
          @expected = expected
        end

        def matches?(target)
          @target = target
          standardize(@target).eql?(standardize(@expected))
        end

        def failure_message
          "expected\n#{@target}\nto match\n#{@expected}"
        end

        def negative_failure_message
          "expected\n#{@target}\nto not match\n#{@expected}" 
        end

        # alphabetize the order of attributes
        def standardize(markup)
          clean_whitespace(markup).gsub(/( [a-zA-Z_]*="[^"])+"/) do |match|
            ' ' + match.strip!.split(' ').sort.join(' ')
          end
        end

        def clean_whitespace(markup)
          markup.
          gsub(/\s+/, ' '). # cleanup whitespace
          gsub(/>\s/, '>'). # kill space after tags
          gsub(/\s</, '<'). # space before tags
          gsub(/\s\/>/, '/>'). # space inside self-closing tags
          strip
        end
      end

      def match_markup(markup)
        MatchMarkup.new(markup)
      end
    end
  end
end

This matcher does two things that are helpful. It standardizes the order of attributes, which is crucial since most of the time HTML attributes are specified in Ruby using a hash and therefore their eventual order can't be guaranteed. It also removes all whitespace, so your specs can be easier on you.

Here's a totally contrived example:

it "should wrap with a div" do
  input = <<-HTML
    <p>
        In order to attain the greatest possible clearness,
        let us return to our example of the railway carriage supposed
        to be travelling uniformly.
    </p>
  HTML
  
  output = <<-HTML
      <div class="klass" id="div_3">
        <p>
          In order to attain the greatest possible clearness,
          let us return to our example of the railway carriage supposed
          to be travelling uniformly.
        </p>
      </div>  
  HTML
  
  wrap_with_div(input, :class => 'klass', :id => 'div_3').should match_markup(output)
end