Skip to content

Commit

Permalink
Merge pull request #109 from flavorjones/flavorjones-dom-testing-html…
Browse files Browse the repository at this point in the history
…-version

Allow user to choose the HTML parser used
  • Loading branch information
rafaelfranca authored Aug 3, 2023
2 parents 90c2418 + 144f8e5 commit 549dc7e
Show file tree
Hide file tree
Showing 10 changed files with 446 additions and 43 deletions.
10 changes: 10 additions & 0 deletions .rdoc_options
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
encoding: UTF-8
charset: UTF-8
exclude:
- "\\.gemspec\\z"
- "gemfiles"
- "Gemfile*"
- "Rakefile"
main_page: "README.md"
markup: rdoc
59 changes: 39 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,6 @@ Doms are compared via `assert_dom_equal` and `assert_dom_not_equal`.
Elements are asserted via `assert_dom`, `assert_dom_encoded`, `assert_dom_email` and a subset of the dom can be selected with `css_select`.
The gem is developed for Rails 4.2 and above, and will not work on previous versions.

## Nokogiri::CSS::SyntaxError exceptions when upgrading to Rails 4.2:

Nokogiri is slightly stricter about the format of CSS selectors than the previous implementation.

Check the 4.2 release notes [section on `assert_select`](http://edgeguides.rubyonrails.org/4_2_release_notes.html#assert-select) for help.

## Installation

Add this line to your application's Gemfile:

gem 'rails-dom-testing'

And then execute:

$ bundle

Or install it yourself as:

$ gem install rails-dom-testing

## Usage

### Dom Assertions
Expand Down Expand Up @@ -53,6 +33,45 @@ assert_dom_email '#you-got-mail'

The documentation in [selector_assertions.rb](https://github.com/rails/rails-dom-testing/blob/master/lib/rails/dom/testing/assertions/selector_assertions.rb) goes into a lot more detail of how selector assertions can be used.

### HTML versions

By default, assertions will use Nokogiri's HTML4 parser.

If `Rails::Dom::Testing.default_html_version` is set to `:html5`, then the assertions will use
Nokogiri's HTML5 parser. (If the HTML5 parser is not available on your platform, then a
`NotImplementedError` will be raised.)

When testing in a Rails application, the parser default can also be set by setting
`Rails.application.config.dom_testing_default_html_version`.

Some assertions support an `html_version:` keyword argument which can override the default for that
assertion. For example:

``` ruby
# compare DOMs built with the HTML5 parser
assert_dom_equal(expected, actual, html_version: :html5)

# compare DOMs built with the HTML4 parser
assert_dom_not_equal(expected, actual, html_version: :html4)
```

Please see documentation for individual assertions for more details.


## Installation

Add this line to your application's Gemfile:

gem 'rails-dom-testing'

And then execute:

$ bundle

Or install it yourself as:

$ gem install rails-dom-testing

## Read more

Under the hood the doms are parsed with Nokogiri, and you'll generally be working with these two classes:
Expand Down
64 changes: 56 additions & 8 deletions lib/rails/dom/testing/assertions/dom_assertions.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require_relative "../parser_selection"

module Rails
module Dom
module Testing
Expand All @@ -8,19 +10,65 @@ module DomAssertions
# \Test two HTML strings for equivalency (e.g., equal even when attributes are in another order)
#
# # assert that the referenced method generates the appropriate HTML string
# assert_dom_equal '<a href="http://www.example.com">Apples</a>', link_to("Apples", "http://www.example.com")
def assert_dom_equal(expected, actual, message = nil, strict: false)
expected_dom, actual_dom = fragment(expected), fragment(actual)
# assert_dom_equal(
# '<a href="http://www.example.com">Apples</a>',
# link_to("Apples", "http://www.example.com"),
# )
#
# By default, the matcher will not pay attention to whitespace in text nodes (e.g., spaces
# and newlines). If you want stricter matching with exact matching for whitespace, pass
# <tt>strict: true</tt>:
#
# # these assertions will both pass
# assert_dom_equal "<div>\nfoo\n\</div>", "<div>foo</div>", strict: false
# assert_dom_not_equal "<div>\nfoo\n\</div>", "<div>foo</div>", strict: true
#
# The DOMs are created using an HTML parser specified by
# Rails::Dom::Testing.default_html_version (either :html4 or :html5).
#
# When testing in a Rails application, the parser default can also be set by setting
# +Rails.application.config.dom_testing_default_html_version+.
#
# If you want to specify the HTML parser just for a particular assertion, pass
# <tt>html_version: :html4</tt> or <tt>html_version: :html5</tt> keyword arguments:
#
# assert_dom_equal expected, actual, html_version: :html5
#
def assert_dom_equal(expected, actual, message = nil, strict: false, html_version: nil)
expected_dom, actual_dom = fragment(expected, html_version: html_version), fragment(actual, html_version: html_version)
message ||= "Expected: #{expected}\nActual: #{actual}"
assert compare_doms(expected_dom, actual_dom, strict), message
end

# The negated form of +assert_dom_equal+.
#
# # assert that the referenced method does not generate the specified HTML string
# assert_dom_not_equal '<a href="http://www.example.com">Apples</a>', link_to("Oranges", "http://www.example.com")
def assert_dom_not_equal(expected, actual, message = nil, strict: false)
expected_dom, actual_dom = fragment(expected), fragment(actual)
# assert_dom_not_equal(
# '<a href="http://www.example.com">Apples</a>',
# link_to("Oranges", "http://www.example.com"),
# )
#
# By default, the matcher will not pay attention to whitespace in text nodes (e.g., spaces
# and newlines). If you want stricter matching with exact matching for whitespace, pass
# <tt>strict: true</tt>:
#
# # these assertions will both pass
# assert_dom_equal "<div>\nfoo\n\</div>", "<div>foo</div>", strict: false
# assert_dom_not_equal "<div>\nfoo\n\</div>", "<div>foo</div>", strict: true
#
# The DOMs are created using an HTML parser specified by
# Rails::Dom::Testing.default_html_version (either :html4 or :html5).
#
# When testing in a Rails application, the parser default can also be set by setting
# +Rails.application.config.dom_testing_default_html_version+.
#
# If you want to specify the HTML parser just for a particular assertion, pass
# <tt>html_version: :html4</tt> or <tt>html_version: :html5</tt> keyword arguments:
#
# assert_dom_not_equal expected, actual, html_version: :html5
#
def assert_dom_not_equal(expected, actual, message = nil, strict: false, html_version: nil)
expected_dom, actual_dom = fragment(expected, html_version: html_version), fragment(actual, html_version: html_version)
message ||= "Expected: #{expected}\nActual: #{actual}"
assert_not compare_doms(expected_dom, actual_dom, strict), message
end
Expand Down Expand Up @@ -84,8 +132,8 @@ def equal_attribute?(attr, other_attr)
end

private
def fragment(text)
Nokogiri::HTML::DocumentFragment.parse(text)
def fragment(text, html_version: nil)
Rails::Dom::Testing.html_document_fragment(html_version: html_version).parse(text)
end
end
end
Expand Down
63 changes: 49 additions & 14 deletions lib/rails/dom/testing/assertions/selector_assertions.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# frozen_string_literal: true

require_relative "../parser_selection"
require_relative "selector_assertions/count_describable"
require_relative "selector_assertions/html_selector"

Expand Down Expand Up @@ -212,7 +213,25 @@ def assert_dom(*args, &block)
# end
# end
# end
def assert_dom_encoded(element = nil, &block)
#
# The DOM is created using an HTML parser specified by
# Rails::Dom::Testing.default_html_version (either :html4 or :html5).
#
# When testing in a Rails application, the parser default can also be set by setting
# +Rails.application.config.dom_testing_default_html_version+.
#
# If you want to specify the HTML parser just for a particular assertion, pass
# <tt>html_version: :html4</tt> or <tt>html_version: :html5</tt> keyword arguments:
#
# assert_dom "feed[xmlns='http://www.w3.org/2005/Atom']" do
# assert_dom "entry>title" do
# assert_dom_encoded(html_version: :html5) do
# assert_dom "b"
# end
# end
# end
#
def assert_dom_encoded(element = nil, html_version: nil, &block)
if !element && !@selected
raise ArgumentError, "Element is required when called from a nonnested assert_dom"
end
Expand All @@ -223,7 +242,7 @@ def assert_dom_encoded(element = nil, &block)
end.map(&:content)
end.join

selected = Nokogiri::HTML::DocumentFragment.parse(content)
selected = Rails::Dom::Testing.html_document_fragment(html_version: html_version).parse(content)
nest_selection(selected) do
if content.empty?
yield selected
Expand All @@ -239,24 +258,40 @@ def assert_dom_encoded(element = nil, &block)
# You must enable deliveries for this assertion to work, use:
# ActionMailer::Base.perform_deliveries = true
#
# assert_dom_email do
# assert_dom "h1", "Email alert"
# end
#
# assert_dom_email do
# items = assert_dom "ol>li"
# items.each do
# # Work with items here...
# end
# end
def assert_dom_email(&block)
# Example usage:
#
# assert_dom_email do
# assert_dom "h1", "Email alert"
# end
#
# assert_dom_email do
# items = assert_dom "ol>li"
# items.each do
# # Work with items here...
# end
# end
#
# The DOM is created using an HTML parser specified by
# Rails::Dom::Testing.default_html_version (either :html4 or :html5).
#
# When testing in a Rails application, the parser default can also be set by setting
# +Rails.application.config.dom_testing_default_html_version+.
#
# If you want to specify the HTML parser just for a particular assertion, pass
# <tt>html_version: :html4</tt> or <tt>html_version: :html5</tt> keyword arguments:
#
# assert_dom_email(html_version: :html5) do
# assert_dom "h1", "Email alert"
# end
#
def assert_dom_email(html_version: nil, &block)
deliveries = ActionMailer::Base.deliveries
assert !deliveries.empty?, "No e-mail in delivery list"

deliveries.each do |delivery|
(delivery.parts.empty? ? [delivery] : delivery.parts).each do |part|
if /^text\/html\W/.match?(part["Content-Type"].to_s)
root = Nokogiri::HTML::DocumentFragment.parse(part.body.to_s)
root = Rails::Dom::Testing.html_document_fragment(html_version: html_version).parse(part.body.to_s)
assert_dom root, ":root", &block
end
end
Expand Down
51 changes: 51 additions & 0 deletions lib/rails/dom/testing/parser_selection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# frozen_string_literal: true

require "active_support"
require "active_support/core_ext/module/attribute_accessors"

module Rails
module Dom
module Testing
mattr_accessor :default_html_version, default: :html4

class << self
def html5_support?
defined?(Nokogiri::HTML5)
end

def html_document(html_version: nil)
parser_classes = { html4: Nokogiri::HTML4::Document }
parser_classes[:html5] = Nokogiri::HTML5::Document if html5_support?

choose_html_parser(parser_classes, html_version: html_version)
end

def html_document_fragment(html_version: nil)
parser_classes = { html4: Nokogiri::HTML4::DocumentFragment }
parser_classes[:html5] = Nokogiri::HTML5::DocumentFragment if html5_support?

choose_html_parser(parser_classes, html_version: html_version)
end

private
def choose_html_parser(parser_classes, html_version: nil)
html_version ||= Rails::Dom::Testing.default_html_version

case html_version
when :html4
parser_classes[:html4]
when :html5
unless Rails::Dom::Testing.html5_support?
raise NotImplementedError, "html5 parser is not supported on this platform"
end
parser_classes[:html5]
else
raise ArgumentError, "html_version must be :html4 or :html5, received #{html_version.inspect}"
end
end
end
end
end
end

require_relative "railtie" if defined?(Rails::Railtie)
14 changes: 14 additions & 0 deletions lib/rails/dom/testing/railtie.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

module Rails
module Dom
module Testing
class Railtie < Rails::Railtie # :nodoc:
config.after_initialize do |app|
version = app.config.try(:dom_testing_default_html_version) # Rails 7.1+
Rails::Dom::Testing.default_html_version = version if version
end
end
end
end
end
Loading

0 comments on commit 549dc7e

Please sign in to comment.