Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make assert_dom_equal ignore insignificant whitespace when walking the node tree #84

Merged
merged 2 commits into from
Jul 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 28 additions & 10 deletions lib/rails/dom/testing/assertions/dom_assertions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,43 +7,61 @@ module DomAssertions
#
# # 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)
def assert_dom_equal(expected, actual, message = nil, strict: false)
expected_dom, actual_dom = fragment(expected), fragment(actual)
message ||= "Expected: #{expected}\nActual: #{actual}"
assert compare_doms(expected_dom, actual_dom), message
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)
def assert_dom_not_equal(expected, actual, message = nil, strict: false)
expected_dom, actual_dom = fragment(expected), fragment(actual)
message ||= "Expected: #{expected}\nActual: #{actual}"
assert_not compare_doms(expected_dom, actual_dom), message
assert_not compare_doms(expected_dom, actual_dom, strict), message
end

protected

def compare_doms(expected, actual)
return false unless expected.children.size == actual.children.size
def compare_doms(expected, actual, strict)
expected_children = extract_children(expected, strict)
actual_children = extract_children(actual, strict)
return false unless expected_children.size == actual_children.size
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assumes that there can't be adjacent text nodes, which is generally not a correct assumption. I think in our use case it's probably a reasonable one, but I am not familiar with the guarantees of the Nokogiri parsing algorithm to know for sure. There may also be ways for us to cause this to happen reject though I could not think of an example.

I don't think there is a strong reason to change this absent of a failing test case, but just thought I should point this out explicitly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I'm misunderstanding what you're referring to, adjacent text nodes should be fine, there just needs to be the same number of nodes.

I suppose it would be possible for "one two three" to be split into 1-3 text nodes, but I doubt that would happen for the same strings in the same process. If we do end up with a different number of text nodes I think there would have to be differences in the markup to cause it and failing would be correct.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's what I meant. I'm not sure if we can guarantee that Nokogiri won't split text nodes, or that our code wouldn't cause it to do that. For example, if we decide to ignore HTML comments, then this would not work, because the comment may have splitter one text node into two. But can find out and iterate on that another time.


expected.children.each_with_index do |child, i|
return false unless equal_children?(child, actual.children[i])
expected_children.each_with_index do |child, i|
return false unless equal_children?(child, actual_children[i], strict)
end

true
end

def equal_children?(child, other_child)
def extract_children(node, strict)
if strict
node.children
else
node.children.reject{|n| n.text? && n.text.blank?}
end
end

def equal_children?(child, other_child, strict)
return false unless child.type == other_child.type

if child.element?
child.name == other_child.name &&
equal_attribute_nodes?(child.attribute_nodes, other_child.attribute_nodes) &&
compare_doms(child, other_child)
compare_doms(child, other_child, strict)
else
equal_child?(child, other_child, strict)
end
end

def equal_child?(child, other_child, strict)
if strict
child.to_s == other_child.to_s
else
child.to_s.split == other_child.to_s.split
end
end

Expand Down
77 changes: 76 additions & 1 deletion test/dom_assertions_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,79 @@ def test_unequal_dom_attributes_in_children
%{<a><b c="2" /></a>}
)
end
end

def test_dom_equal_with_whitespace_strict
canonical = %{<a><b>hello</b> world</a>}
assert_dom_not_equal(canonical, %{<a>\n<b>hello\n </b> world</a>}, strict: true)
assert_dom_not_equal(canonical, %{<a> \n <b>\n hello</b> world</a>}, strict: true)
assert_dom_not_equal(canonical, %{<a>\n\t<b>hello</b> world</a>}, strict: true)
assert_dom_equal(canonical, %{<a><b>hello</b> world</a>}, strict: true)
end

def test_dom_equal_with_whitespace
canonical = %{<a><b>hello</b> world</a>}
assert_dom_equal(canonical, %{<a>\n<b>hello\n </b> world</a>})
assert_dom_equal(canonical, %{<a>\n<b>hello </b>\nworld</a>})
assert_dom_equal(canonical, %{<a> \n <b>\n hello</b> world</a>})
assert_dom_equal(canonical, %{<a> \n <b> hello </b>world</a>})
assert_dom_equal(canonical, %{<a> \n <b>hello </b>world\n</a>\n})
assert_dom_equal(canonical, %{<a>\n\t<b>hello</b> world</a>})
assert_dom_equal(canonical, %{<a>\n\t<b>hello </b>\n\tworld</a>})
end

def test_dom_equal_with_attribute_whitespace
canonical = %(<div data-wow="Don't strip this">)
assert_dom_equal(canonical, %(<div data-wow="Don't strip this">))
assert_dom_not_equal(canonical, %(<div data-wow="Don't strip this">))
end

def test_dom_equal_with_indentation
canonical = %{<a>hello <b>cruel</b> world</a>}
assert_dom_equal(canonical, <<-HTML)
<a>
hello
<b>cruel</b>
world
</a>
HTML

assert_dom_equal(canonical, <<-HTML)
<a>
hello
<b>cruel</b>
world
</a>
HTML

assert_dom_equal(canonical, <<-HTML)
<a>hello
<b>
cruel
</b>
world</a>
HTML
end

def test_dom_equal_with_surrounding_whitespace
canonical = %{<p>Lorem ipsum dolor</p><p>sit amet, consectetur adipiscing elit</p>}
assert_dom_equal(canonical, <<-HTML)
<p>
Lorem
ipsum
dolor
</p>

<p>
sit amet,
consectetur
adipiscing elit
</p>
HTML
end

def test_dom_not_equal_with_interior_whitespace
with_space = %{<a><b>hello world</b></a>}
without_space = %{<a><b>helloworld</b></a>}
assert_dom_not_equal(with_space, without_space)
end
end