Created
May 16, 2020 03:23
-
-
Save Phlip/5dbc7f5ac25f74591f3754b9fd6b8d66 to your computer and use it in GitHub Desktop.
assert_xpath, assert_latest, assert_yin_yang
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'test_helper' | |
require 'nokogiri' | |
require 'pp' | |
class WelcomeControllerTest < ActionController::TestCase | |
# Someone currently working in Ruby on Rails or similar should | |
# put these into Ruby gems ktx | |
## | |
# +assert_xml+ validates XML in its string argument, +xml+, and prepares it for | |
# further testing with +assert_xpath+. It optionally raises a Minitest assertion | |
# failure with any XML syntax errors it finds. | |
# | |
# ==== Parameters | |
# | |
# * +xml+ - A string containing XML. | |
# * +strict+ - Optional boolean deciding whether to raise syntax errors. Defaults to +true+. | |
# | |
# ==== Returns | |
# | |
# If the XML passes inspection, it places the XML's Document Object Model into | |
# the variable <code>@selected</code>, for +assert_xpath+ and +refute_xpath+ to | |
# interrogate. | |
# | |
# Finally, it returns <code>@selected</code>, for custom testing. | |
# | |
def assert_xml(xml, strict = true) | |
@selected = Nokogiri::XML(xml) | |
assert @selected.xml?, 'Nokogiri should identify this as XML.' | |
strict and _assert_no_xml_or_html_syntax_errors(xml) | |
return @selected | |
end | |
## | |
# +assert_html+ validates an HTML string, and prepares it for further | |
# testing with +assert_xpath+ or +refute_xpath+. | |
# | |
# ==== Parameters | |
# | |
# * +html+ - Optional string of HTML. Defaults to <code>response.body</code>. | |
# * +strict+ - Optional boolean deciding whether to raise syntax errors. Defaults to +true+. | |
# | |
# ==== Examples | |
# | |
# Call +assert_html+ in one of several ways. | |
# | |
# In a test environment such as a test suite derived from | |
# +ActionController::TestCase+ or +ActionDispatch::IntegrationTest+, if a call | |
# such as <code>get :action</code> has prepared the +response+ instance variable, | |
# you may call +assert_html+ invisibly, by letting +assert_xpath+ call it for you: | |
# | |
# get :new | |
# assert_xpath '//form[ "/create" = @action ]' | |
# | |
# In that mode, +assert_html+ will raise a Minitest failure if the HTML contains | |
# a syntax error. If you cannot fix this error, you can reduce +assert_html+'s | |
# aggressiveness by calling it directly with +false+ in its second parameter: | |
# | |
# get :new | |
# assert_html response.body, false | |
# assert_xpath '//form[ "/create" = @action ]' | |
# | |
# ==== Returns | |
# | |
# +assert_html+ returns the <code>@selected</code> Document Object Model root element, | |
# for custom testing. | |
# | |
def assert_html(html = nil, strict = true) | |
html ||= response.body | |
@selected = Nokogiri::HTML(html) | |
assert @selected.html?, 'Nokogiri should identify this as HTML.' | |
if strict | |
_assert_no_xml_or_html_syntax_errors(html) | |
if strict == :html5 || html =~ /\A\s*<!doctype html>/i | |
deprecated = %w(acronym applet basefont big center dir font frame | |
frameset noframes isindex nobr menu s strike tt u) | |
deprecated.each do |dep| | |
refute_xpath "//#{dep}", "The <#{dep}> element is deprecated." | |
end | |
deprecated_attributes = [ | |
[ 'height', [ 'table', 'tr', 'th', 'td' ] ], | |
[ 'align', %w(caption iframe img input object legend table | |
hr div h1 h2 h3 h4 h5 h6 | |
p col colgroup tbody td tfoot th thead tr) ], | |
[ 'valign', [ 'td' 'th' ] ], | |
[ 'width', [ 'hr', 'table', 'td', 'th', 'col', 'colgroup', 'pre' ] ], | |
[ 'name', [ 'img' ] ] | |
] | |
deprecated_attributes.each do |attr, tags| | |
tags.each do |tag| | |
refute_xpath "//#{tag}[ @#{attr} ]", "The <#{tag} #{attr}> attribute is deprecated." | |
end | |
end | |
refute_xpath '//body//style', '<style> tags must be in the <head>.' | |
refute_xpath '//table/tr', '<table> element missing <thead> or <tbody>.' | |
refute_xpath '//td[ not( parent::tr ) ]', '<td> element without <tr> parent.' | |
refute_xpath '//th[ not( parent::tr ) ]', '<th> element without <tr> parent.' | |
# TODO fix Warning: <input> anchor "pc_contract_create_activity_" already defined | |
# in pc_contracts_controller new | |
# refute_xpath '//img[ @full_size ]', '<img> contains proprietary attribute "full_size".' | |
# CONSIDER tell el-Goog not to do this: refute_xpath '//textarea[ @value ]', '<textarea> contains proprietary attribute "value".' | |
# CONSIDER tell el-Goog not to do this: refute_xpath '//iframe[ "" = @src ]', '<iframe> contains empty attribute "src".' | |
# A document must not include both a meta element with an http-equiv attribute whose value is content-type, and a meta element with a charset attribute. | |
# Consider avoiding viewport values that prevent users from resizing documents. | |
# | |
# From line 9, column 1; to line 9, column 103 | |
# | |
# s</title>↩<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">↩<meta | |
# | |
# The type attribute is unnecessary for JavaScript resources. | |
# | |
# From line 394, column 9; to line 394, column 46 | |
# | |
# >↩ <script type="application/javascript">↩ | |
# TODO tidy sez: Warning: <a> escaping malformed URI reference | |
# TODO tidy sez: Warning: <a> illegal characters found in URI | |
end | |
end | |
return @selected | |
end | |
def _assert_no_xml_or_html_syntax_errors(xml) #:nodoc: | |
errs = | |
@selected.errors.map do |error| | |
if error.level > 0 | |
err = "#{error.level > 1 ? 'Error' : 'Warning'} on line #{error.line}, column #{error.column}: code #{error.code}, level #{error.level}\n" | |
err << "#{error}\n" | |
error.str1.present? and err << "#{error.str1}\n" | |
error.str2.present? and err << "#{error.str2}\n" | |
error.str3.present? and err << "#{error.str3}\n" | |
err << xml.lines[error.line - 1].rstrip + "\n" | |
error.column > 1 and err << ('-' * (error.column - 1)) | |
err << "^\n" | |
err | |
else | |
'' | |
end | |
end | |
errs = errs.join("\n") | |
errs.present? and raise Minitest::Assertion, errs | |
end | |
private :_assert_no_xml_or_html_syntax_errors | |
## | |
# After calling +assert_xml+ or +assert_html+, or calling a test method that loads | |
# +response.body+, call +assert_xpath+ to query XML's or HTML's Document Object | |
# Model and interrogate its tags, attributes, and contents. | |
# | |
# ==== Parameters | |
# | |
# * +path+ - Required XPath string. | |
# * +replacements+ - Optional Hash of replacement keys and values. | |
# * +message+ - Optional string or ->{block} containing additional diagnostics. | |
# * <code>&block</code> - Optional block to call with a selected context. | |
# | |
# ==== Returns | |
# | |
# +assert_xpath+ returns the first <code>Nokogiri::XML::Element</code> it finds matching its | |
# XPath, and it yields this element to any block it is called with. Note that Nokogiri | |
# supplies many useful methods on this element, including +.text+ to access its text | |
# contents, and <code>[]</code> to access its attributes, such as the below | |
# <code>form[:method]</code>. And because the method +.to_s+ returns an element's outer | |
# HTML, a debugging trace line like <code>puts assert_xpath('td[ 2 ]')</code> will | |
# output the selected element's HTML. | |
# | |
# ==== Yields | |
# | |
# You may call +assert_select+ and pass it a block containing +assert_xpath+, | |
# and +assert_xpath+ optionally yields to a block where you can call +assert_xpath+ | |
# and +assert_select+. Each block restricts the context which XPath or CSS selectors | |
# can access: | |
# | |
# get :index | |
# assert_select 'td#contact_form' do | |
# assert_xpath 'form[ "/contact" = @action ]' do |form| | |
# assert_equal 'post', form[:method] | |
# assert_xpath './/input[ "name" = @name and "text" = @type and "" = @value ]' | |
# assert_select 'input[type=submit][name=commit]' | |
# end | |
# end | |
# | |
# A failure inside one of those +assert_xpath+ blocks will report only its XML or HTML | |
# context in its failure messages. | |
# | |
# ==== Operations | |
# | |
# Note that an XPath of <code>'form'</code> finds an immediate child of the current | |
# context, while a CSS selector of <code>'form'</code> will find any descendant. And | |
# note that, although an XPath of <code>'//input'</code> will find any 'input' in | |
# the current document, only a relative XPath of <code>'.//input'</code> will find | |
# only the descendants of the current context. | |
# | |
# Warning: At failure time, +assert_xpath+ prints out the XML or HTML context where your | |
# XPath failed. However, as a convenience for reading this source, +assert_xpath+ uses | |
# Nokogiri to reformat the source, and to expand entities such as <code>&</code>. This | |
# means the diagnostic message won't exactly match the input. It's better than nothing. | |
# | |
# Consult an XPath reference to learn the full power of the queries possible. Here are | |
# some examples: | |
# | |
# assert_xpath '//select[ "names" = @name and 26 = count( option ) ]' do | |
# assert_xpath 'option[ 1 ][ "Able" = text() ]', 'Must be first.' | |
# assert_xpath 'option[ 2 ][ "Baker" = text() and not( @selected ) ]' | |
# assert_xpath 'option[ 3 ][ "Charlie" = text() and @selected ]' | |
# assert_xpath 'option[ last() ][ "Zed" = text() ]' | |
# end | |
# assert_xpath './/textarea[ "message" = @name and not( text() ) ]' | |
# assert_xpath '/html/head/title[ contains( text(), "Contact us" ) ]' | |
# em = assert_xpath('td/em') | |
# assert_match /No members/, em.text | |
# assert_xpath 'div[ 1 ][ not( * ) ]', 'The first div must have no children.' | |
# assert_xpath '//p[ label[ "type_id" = @for ] ]' # Returns the p not the label | |
# | |
# +assert_xpath+ accepts a Hash of replacement values for its second or third | |
# argument. Use this to inject strings that are long, or contain quotes ' ", | |
# or are generated. A Hash key of +:id+ will inject its value into an XPath of | |
# <code>$id</code>: | |
# | |
# assert_xpath 'label/input[ "radio" = @type and $value = @value and $id = @id ]', | |
# value: type.id, id: "type_id_#{type.id}" | |
# | |
# Finally, +assert_xpath+ accepts a message string or callable, to provide extra | |
# diagnostics in its failure message. This message could be the last argument, but | |
# passing the replacements hash last is sometimes more convenient: | |
# | |
# assert_xpath 'script[ $amount = @data-amount ]', | |
# 'The script must contain a data-amount in pennies', | |
# amount: 100 | |
# | |
# Pass a ->{block} for the message argument if it is expensive and you don't want | |
# it to slow down successful tests: | |
# | |
# assert_xpath 'script[ $amount = @data-amount ]', | |
# ->{ generate_extra_diagnostics_for_amount(100) }, | |
# amount: 100 | |
# | |
def assert_xpath(path, replacements = {}, message = nil, &block) | |
replacements, message = _get_xpath_arguments(replacements, message) | |
element = @selected.at_xpath(path, nil, replacements) | |
element or _flunk_xpath(path, '', replacements, message) | |
if block | |
begin | |
waz_selected = @selected | |
@selected = element | |
block.call(element) | |
ensure | |
@selected = waz_selected | |
end | |
end | |
pass # Increment the test runner's assertion count. | |
return element | |
end | |
## | |
# See +assert_xpath+ to learn what contexts can call +refute_xpath+. This | |
# assertion fails if the given XPath query can find an element in the current | |
# <code>@selected</code> or +response.body+ context. | |
# | |
# ==== Parameters | |
# | |
# Like +assert_xpath+ it takes an XPath string, an optional Hash of | |
# replacements, and an optional message as a string or callable: | |
# | |
# refute_xpath '//form[ $action = @action ]', | |
# { action: "/users/#{user.id}/cancel" }, | |
# ->{ 'The Cancel form must not appear yet' } | |
# | |
# ==== Returns | |
# | |
# Unlike +assert_xpath+, +refute_xpath+ naturally does not yield to a block | |
# or return an element. | |
# | |
def refute_xpath(path, replacements = {}, message = nil) | |
replacements, message = _get_xpath_arguments(replacements, message) | |
element = @selected.at_xpath(path, nil, replacements) | |
element and _flunk_xpath(path, 'not ', replacements, message) | |
pass # Increment the test runner's assertion count. | |
return nil # there it is; the non-element! | |
end | |
def _get_xpath_arguments(replacements, message) #:nodoc: | |
@selected ||= nil # Avoid a dumb warning. | |
@selected or assert_html # Because assert_html snags response.body for us. | |
message_is_replacements = message.is_a?(Hash) | |
replacements_is_message = replacements.is_a?(String) || replacements.respond_to?(:call) | |
replacements, message = message, replacements if message_is_replacements || replacements_is_message | |
# Nokogiri requires all replacement values to be strings... | |
replacements ||= {} | |
replacements = replacements.merge(replacements){ |_, _, v| v.to_s } | |
return replacements, message | |
end | |
private :_get_xpath_arguments | |
def _flunk_xpath(path, polarity, replacements, message) #:nodoc: | |
message = message.respond_to?(:call) ? message.call : message | |
diagnostic = message.to_s | |
diagnostic.length > 0 and diagnostic << "\n" | |
element = Array.wrap(@selected)[0] | |
pretty = element.xml? ? element.to_xml : element.to_xhtml | |
diagnostic << "Element #{polarity}expected in:\n`#{pretty}`\nat xpath:\n`#{path}`" | |
replacements.any? and diagnostic << "\nwith: " + replacements.pretty_inspect | |
raise Minitest::Assertion, diagnostic | |
end | |
private :_flunk_xpath | |
## | |
# When a test case calls methods that write new ActiveModel records to a database, | |
# sometimes the test needs to assert those records were created, by fetching them back | |
# for inspection. +assert_latest_record+ collects every record in the given model or | |
# models that appear while its block runs, and returns either a single record or a ragged | |
# array. | |
# | |
# ==== Parameters | |
# | |
# * +models+ - At least 1 ActiveRecord model or association. | |
# * +message+ - Optional string or ->{block} to provide more diagnostics at failure time. | |
# * <code>&block</code> - Required block to call and monitor for new records. | |
# | |
# ==== Example | |
# | |
# user, email_addresses = | |
# assert_latest_record User, EmailAddress, ->{ 'Need moar records!' } do | |
# post :create, ... | |
# end | |
# assert_equal 'franklyn', user.login # 1 user, so not an array | |
# assert_equal 2, email_addresses.length | |
# assert_equal '[email protected]', email_addresses.first.mail | |
# assert_equal '[email protected]', email_addresses.second.mail | |
# | |
# ==== Returns | |
# | |
# The returned value is a set of one or more created records. The set is normalized, | |
# so all arrays of one item are replaced with the item itself. | |
# | |
# ==== Operations | |
# | |
# The last argument to +assert_latest_record+ can be a string or a callable block. | |
# At failure time the assertion adds this string or this block's return value to | |
# the diagnostic message. | |
# | |
# You may call +assert_latest_record+ with anything that responds to <code>.pluck(:id)</code> | |
# and <code>.where()</code>, including ActiveRecord associations: | |
# | |
# user = User.last | |
# email_address = | |
# assert_latest_record user.email_addresses do | |
# post :add_email_address, user_id: user.id, ... | |
# end | |
# assert_equal '[email protected]', email_address.mail | |
# assert_equal email_address.user_id, user.id, 'This assertion is redundant.' | |
# | |
# +assert_latest_record+ also works on records with generated non-sequential and multiple | |
# primary keys, such as GUIDs or join tables. | |
# | |
def assert_latest_record(*models, &block) | |
models, message = _get_latest_record_args(models, 'assert') | |
latests = _get_latest_record(models, block) | |
latests.include?(nil) and _flunk_latest_record(models, latests, message, true) | |
pass # Increment the test runner's assertion count | |
return latests.length > 1 ? latests : latests.first | |
end | |
## | |
# When a test case calls methods that might write new ActiveModel records to a | |
# database, sometimes the test must check that no records were written. | |
# +refute_latest_record+ watches for new records in the given class or classes | |
# that appear while its block runs, and fails if any appear. | |
# | |
# ==== Parameters | |
# | |
# See +assert_latest_record+. | |
# | |
# ==== Operations | |
# | |
# refute_latest_record User, EmailAddress, ->{ 'GET should not create records' } do | |
# get :index | |
# end | |
# | |
# The last argument to +refute_latest_record+ can be a string or a callable block. | |
# At failure time the assertion adds this string or this block's return value to | |
# the diagnostic message. | |
# | |
# Like +assert_latest_record+, you may call +refute_latest_record+ with anything | |
# that responds to <code>pluck(:id)</code> and <code>where()</code>, including | |
# ActiveRecord associations. And, like +assert_latest_record+, it works on | |
# records with generated and multiple primary keys, such as GUIDs or join tables. | |
# | |
def refute_latest_record(*models, &block) | |
models, message = _get_latest_record_args(models, 'refute') | |
latests = _get_latest_record(models, block) | |
latests.all?(&:nil?) or _flunk_latest_record(models, latests, message, false) | |
pass | |
return | |
end | |
## | |
# Sometimes a test must detect new records without using an assertion that passes | |
# judgment on whether they should have been written. Call +get_latest_record+ to | |
# return a ragged array of records created during its block, or +nil+: | |
# | |
# user, email_addresses, posts = | |
# get_latest_record User, EmailAddress, Post do | |
# post :create, ... | |
# end | |
# | |
# assert_nil posts, "Don't create Post records while creating a User" | |
# | |
# Unlike +assert_latest_record+, +get_latest_record+ does not take a +message+ string | |
# or block, because it has no diagnostic message. | |
# | |
# Like +assert_latest_record+, you may call +get_latest_record+ with anything | |
# that responds to <code>.pluck(:id)</code> and <code>.where()</code>, including | |
# ActiveRecord associations. And, like +assert_latest_record+, it works on | |
# records with generated and multiple primary keys, such as GUIDs or join tables. | |
# | |
def get_latest_record(*models, &block) | |
assert models.any?, 'Call get_latest_record with one or more ActiveRecord models or associations.' | |
refute_nil block, 'Call get_latest_record with a block.' | |
records = _get_latest_record(models, block) | |
return records.length > 1 ? records : records.first | |
end # Methods should be easy to use correctly and hard to use incorrectly... | |
def _get_latest_record_args(models, what) #:nodoc: | |
message = nil | |
message = models.pop unless models.last.respond_to?(:pluck) | |
valid_message = message.nil? || message.kind_of?(String) || message.respond_to?(:call) | |
models.length > 0 && valid_message and return models, message | |
raise "call #{what}_latest_record(models..., message) with any number\n" + | |
'of Model classes or associations, followed by an optional diagnostic message' | |
end | |
private :_get_latest_record_args | |
def _get_latest_record(models, block) #:nodoc: | |
id_sets = models.map{ |model| model.pluck(*model.primary_key) } # Sorry about your memory! | |
block.call | |
record_sets = [] | |
models.each_with_index do |model, index| | |
pk = model.primary_key | |
set = id_sets[index] | |
records = | |
if set.length == 0 | |
model | |
elsif pk.is_a?(Array) | |
pks = pk.map{ |k| "`#{k}` = ?" }.join(' AND ') | |
pks = [ "(#{pks})" ] * set.length | |
pks = pks.join(' OR ') | |
model.where.not(pks, *set.flatten) | |
else | |
model.where.not(pk => set) | |
end | |
records = records.order(*pk).to_a | |
record_sets.push records.length > 1 ? records : records.first | |
end | |
return record_sets | |
end | |
private :_get_latest_record | |
def _flunk_latest_record(models, latests, message, polarity) #:nodoc: | |
itch_list = [] | |
models.each_with_index do |model, index| | |
records_found = latests[index] != nil | |
records_found == polarity or itch_list << model.name | |
end | |
itch_list = itch_list.join(', ') | |
diagnostic = "should#{' not' unless polarity} create new #{itch_list} record(s) in block" | |
message = nil if message == '' | |
message = message.call.to_s if message.respond_to?(:call) | |
message = [ message, diagnostic ].compact.join("\n") | |
raise Minitest::Assertion, message | |
end | |
private :_flunk_latest_record | |
def assert_yin_yang(*args, proc) | |
args.empty? and args = [false, true] | |
explain = 'Call assert_yin_yang with none, one, or two value arguments and a procedure.' | |
assert args.length < 3 && proc.respond_to?(:call), explain | |
yin_result = proc.call | |
message = "Expression expected to change from #{args.first.inspect} to #{args.last.inspect} but failed " | |
if args.first.nil? | |
assert_nil yin_result, message + 'before yield' | |
else | |
assert_equal args.first, yin_result, message + 'before yield' | |
end | |
x = yield | |
yang_result = proc.call | |
if args.last.nil? | |
assert_nil yang_result, message + 'after yield' | |
else | |
assert_equal args.last, yang_result, message + 'after yield' | |
end | |
return x | |
end | |
def test_strict_Nokogiri | |
html = '<script> | |
let buttonDelete = $(`<a class="icon icon-del" href="#"></a>`); | |
</script>' | |
doc = Nokogiri::HTML(html, nil, nil, Nokogiri::XML::ParseOptions::STRICT) | |
# assert_empty doc.errors CONSIDER see https://stackoverflow.com/questions/49683104/well-formed-scriptjavascript-script-tags-confuse-nokogirihtml-in-strict-mo | |
# Then see: https://bugzilla.gnome.org/show_bug.cgi?id=795390 | |
end | |
def test_assert_xml | |
bad_xml = '<a><b' | |
doc = Nokogiri::XML(bad_xml) | |
assert_equal 2, doc.errors.length # CONSIDER use .length not .size | |
error = doc.errors.first | |
assert_equal 3, error.level | |
assert error.fatal? | |
assert_equal 1, error.line | |
assert_equal 6, error.column | |
assert_match /1:6: FATAL: Couldn't find end of Start Tag b line 1/, error.to_s | |
assert_equal 73, error.code | |
assert_equal 'b', error.str1 | |
assert_nil error.str2 | |
assert_nil error.str3 | |
error = doc.errors.second | |
assert_equal 3, error.level | |
assert error.fatal? | |
assert_equal 1, error.line | |
assert_equal 6, error.column | |
assert_match /1:6: FATAL: Premature end of data in tag a line 1/, error.to_s | |
assert_equal 77, error.code | |
assert_equal 'a', error.str1 | |
assert_nil error.str2 | |
assert_nil error.str3 | |
e = | |
assert_raises Minitest::Assertion do | |
assert_xml bad_xml | |
end | |
assert_match /Couldn't find end of Start Tag b line 1/, e.message | |
assert_match /Premature end of data in tag a line 1/, e.message | |
end | |
# def test_assert_xpath | |
# assert_xml '<a><b></b><b id="42"></b></a>' | |
# | |
# e = | |
# assert_raises Minitest::Assertion do | |
# | |
# assert_xpath '/a/b[ $id = @id ]', { id: 43 }, ->{ 'Tom Lehrer' } | |
# | |
# end | |
# | |
# assert_match /Tom Lehrer/, e.message | |
# assert_includes @selected.to_s, e.message | |
# assert_includes ':id=>"43"', e.message | |
# end | |
# def test_assert_xpath_with_reversed_arguments | |
# assert_xml '<a><b></b><b id="42"></b></a>' | |
# | |
# e = | |
# assert_raises Minitest::Assertion do | |
# | |
# assert_xpath '/a/b[ $id = @id ]', ->{ 'Insect Surfers' }, id: 43 | |
# | |
# end | |
# | |
# assert_match /Insect Surfers/, e.message | |
# assert_includes @selected.to_s, e.message | |
# assert_includes ':id=>"43"', e.message | |
# end | |
# def test_refute_xpath | |
# assert_xml '<a><b></b><b id="42"></b></a>' | |
# | |
# e = | |
# assert_raises Minitest::Assertion do | |
# | |
# refute_xpath '/a/b[ $id = @id ]', { id: '42' }, ->{ 'Bart and the Bedazzle' } | |
# | |
# end | |
# | |
# assert_match /Bart and the Bedazzle/, e.message | |
# assert_includes @selected.to_s, e.message | |
# assert_includes ':id=>"42"', e.message | |
# end | |
# def test_refute_xpath_with_reversed_arguments | |
# assert_xml '<a><b></b><b id="42"></b></a>' | |
# | |
# e = | |
# assert_raises Minitest::Assertion do | |
# | |
# refute_xpath '/a/b[ $id = @id ]', ->{ 'Noam Chomsky' }, id: '42' | |
# | |
# end | |
# | |
# assert_match /Noam Chomsky/, e.message | |
# assert_includes @selected.to_s, e.message | |
# assert_includes ':id=>"42"', e.message | |
# end | |
# def test_assert_latest_record | |
# e = | |
# assert_raises Minitest::Assertion do | |
# | |
# assert_latest_record Product, TimeEntry, ->{ 'permatrails' } do | |
# # post :create # oops! | |
# end | |
# | |
# end | |
# | |
# assert_match /^permatrails$/, e.message | |
# assert_match /should create new PcProduct, TimeEntry record\(s\) in block/, e.message | |
# end | |
test "should get index" do | |
get :index | |
assert_response :success | |
assert_select 'h1' | |
assert_select 'ul' do | |
assert_xpath 'li[ "frogs" = text() ]' | |
end | |
x = 7 | |
assert_yin_yang ->{ x == 8 } do | |
x = 8 | |
end | |
assert_yin_yang true, false, ->{ x == 8 } do | |
x = 7 | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment