While writing a project for Wilcox Tech that we hope to open source soon, I had reason to test the content (or, in some cases, presence) of <meta/> tags in a Rails app.
I came across a good start in dB.’s blog article, Custom RSpec Meta Tag Validator. However, the article was from 2013, did not follow the modern RSpec protocol, and the matcher felt a bit off for my purposes.
What I really wanted was a matcher that felt like the built-in ones to Rails, such as assert_dom or assert_select. So, using dB.’s XPath query as a starting point, I wrote a new one.
I am under the assumption that dB.’s snippet is licensed CC-BY as is the rest of his post. I am therefore licensing my matcher under a dual-license, as I believe this to be legal: CC-BY with my authorship, or MIT (the standard license of most Rails-related Ruby Gems). To fully comply, you will need to acknowledge him as well.
Usage looks like this:
describe 'meta product properties' do
before { assign(:item, create(:item, description: 'This is a test description.', price: 50.50)) }
it 'has the correct Open Graph type' do
render_item
expect(rendered).to have_meta 'og:type', 'product'
end
it 'has the description set' do
render_item
expect(rendered).to have_meta 'og:description', 'This is a test description'
end
it 'has the price set' do
render_item
expect(rendered).to have_meta 'product:price.amount', '50.50'
end
end
describe 'meta image properties' do
context 'with no photos' do
it 'does not have an og:image property' do
assign(:item, create(:item))
render_item
expect(rendered).not_to have_meta 'og:image'
end
end
context 'with a photo' do
before do
item = create(:item)
photo = mock_item_photo_for item
photo.description = ‘My Photo Description’
assign(:item, item)
end
it 'has an og:image property' do
render_item
expect(rendered).to have_meta 'og:image'
end
it 'has the og:image:alt property set to the photo description' do
render_item
expect(rendered).to have_meta 'og:image:alt', 'My Photo Description'
end
end
end
And here’s my spec/support/matchers/have_meta.rb:
# frozen_string_literal: true
class HaveMeta
attr_accessor :expected, :actual, :key
def initialize(*args)
raise ArgumentError, 'Need at least one argument' if args.empty?
@key = args.shift
@expected = args.shift
end
def diffable?
@expected.present?
end
def matches?(page)
meta = page.html.at_xpath("//head/meta[@property='#{@key}' or @name='#{@key}']")
return false if meta.blank?
return true if @expected.blank?
@actual = meta['content']
@actual.include? @expected
end
def failure_message
return "expected meta property '#{key}'" if @expected.blank?
"expected '#{key}' to contain '#{@expected}' in '#{@actual}'"
end
def failure_message_when_negated
return "expected not to find meta property '#{key}'" if @expected.blank?
"expected '#{key}' to not contain '#{@expected}' in '#{@actual}'"
end
end
# Determines if the rendered page has a given meta tag.
def have_meta(*args)
HaveMeta.new(*args)
end
