diff -pruN 2.2.0-1/README.md 2.3.0-1/README.md
--- 2.2.0-1/README.md	2024-01-12 18:08:49.000000000 +0000
+++ 2.3.0-1/README.md	2025-06-18 03:10:37.000000000 +0000
@@ -24,6 +24,9 @@ css_select '.hello' # => Nokogiri::XML::
 # select from a supplied node. assert_dom asserts elements exist.
 assert_dom document_root_element.at('.hello'), '.goodbye'
 
+# select from a supplied node. assert_not_dom asserts elements do not exist.
+assert_not_dom document_root_element.at('.hello'), '.goodbye'
+
 # elements in CDATA encoded sections can also be selected
 assert_dom_encoded '#out-of-your-element'
 
diff -pruN 2.2.0-1/debian/changelog 2.3.0-1/debian/changelog
--- 2.2.0-1/debian/changelog	2025-01-14 17:38:27.000000000 +0000
+++ 2.3.0-1/debian/changelog	2025-08-14 23:10:11.000000000 +0000
@@ -1,3 +1,24 @@
+ruby-rails-dom-testing (2.3.0-1) unstable; urgency=medium
+
+  * New upstream release.
+  * Remove unused debian/patches/ directory.
+  * Remove problematic debian/.gitattributes.
+  * debian/control:
+    - Add myself as an uploader.
+    - Add Build-Depends on dh-sequence-ruby.
+    - Move rake, ruby-activesupport, ruby-minitest, and ruby-nokogiri to
+      Build-Depends-Indep.
+    - Remove obsolete version constraints on gem2deb, ruby-activesupport, and
+      ruby-nokogiri.
+    - Remove obsolete "Rules-Requires-Root: no".
+    - Bump Standards-Version from 4.7.0 to 4.7.2, no changes needed.
+  * debian/copyright:
+    - Add myself to the "Files: debian/*" stanza.
+    - Reformat for readability.
+  * debian/rules:  Remove obsolete "--with ruby".
+
+ -- Soren Stoutner <soren@debian.org>  Thu, 14 Aug 2025 16:10:11 -0700
+
 ruby-rails-dom-testing (2.2.0-1) unstable; urgency=medium
 
   * Team upload
diff -pruN 2.2.0-1/debian/control 2.3.0-1/debian/control
--- 2.2.0-1/debian/control	2025-01-14 17:38:27.000000000 +0000
+++ 2.3.0-1/debian/control	2025-08-14 23:05:14.000000000 +0000
@@ -2,19 +2,20 @@ Source: ruby-rails-dom-testing
 Section: ruby
 Priority: optional
 Maintainer: Debian Ruby Team <pkg-ruby-extras-maintainers@lists.alioth.debian.org>
-Uploaders: Sruthi Chandran <srud@disroot.org>
+Uploaders: Soren Stoutner <soren@debian.org>,
+           Sruthi Chandran <srud@disroot.org>
 Build-Depends: debhelper-compat (= 13),
-               gem2deb (>= 1),
-               rake,
-               ruby-activesupport (>= 2:5.0.0),
-               ruby-minitest,
-               ruby-nokogiri (>= 1.6)
-Standards-Version: 4.7.0
+               dh-sequence-ruby,
+               gem2deb
+Build-Depends-Indep: rake,
+                     ruby-activesupport,
+                     ruby-minitest,
+                     ruby-nokogiri
 Vcs-Git: https://salsa.debian.org/ruby-team/ruby-rails-dom-testing.git
 Vcs-Browser: https://salsa.debian.org/ruby-team/ruby-rails-dom-testing
 Homepage: https://github.com/rails/rails-dom-testing
 Testsuite: autopkgtest-pkg-ruby
-Rules-Requires-Root: no
+Standards-Version: 4.7.2
 
 Package: ruby-rails-dom-testing
 Architecture: all
diff -pruN 2.2.0-1/debian/copyright 2.3.0-1/debian/copyright
--- 2.2.0-1/debian/copyright	2025-01-14 17:38:27.000000000 +0000
+++ 2.3.0-1/debian/copyright	2025-08-14 22:55:05.000000000 +0000
@@ -3,30 +3,31 @@ Upstream-Name: rails-dom-testing
 Source: https://github.com/rails/rails-dom-testing
 
 Files: *
-Copyright: (c) 2013-2015 Kasper Timm Hansen
+Copyright: 2013-2015 Kasper Timm Hansen
 License: Expat
 
 Files: debian/*
 Copyright: 2015-2025 Antonio Terceiro <terceiro@debian.org>
+           2025 Soren Stoutner <soren@debian.org>
 License: Expat
-Comment: the Debian packaging is licensed under the same terms as the original package.
+Comment: the Debian packaging is licensed under the same terms as the original
+ package.
 
 License: Expat
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
+ this software and associated documentation files (the "Software"), to deal in
+ the Software without restriction, including without limitation the rights to
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ of the Software, and to permit persons to whom the Software is furnished to do
+ so, subject to the following conditions:
  .
- The above copyright notice and this permission notice shall be
- included in all copies or substantial portions of the Software.
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
  .
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
diff -pruN 2.2.0-1/debian/rules 2.3.0-1/debian/rules
--- 2.2.0-1/debian/rules	2025-01-14 17:38:27.000000000 +0000
+++ 2.3.0-1/debian/rules	2025-08-14 23:05:09.000000000 +0000
@@ -4,4 +4,4 @@ export GEM2DEB_TEST_RUNNER = --check-dep
 export DH_RUBY = --gem-install
 
 %:
-	dh $@ --buildsystem=ruby --with ruby
+	dh $@ --buildsystem=ruby
diff -pruN 2.2.0-1/lib/rails/dom/testing/assertions/dom_assertions.rb 2.3.0-1/lib/rails/dom/testing/assertions/dom_assertions.rb
--- 2.2.0-1/lib/rails/dom/testing/assertions/dom_assertions.rb	2024-01-12 18:08:49.000000000 +0000
+++ 2.3.0-1/lib/rails/dom/testing/assertions/dom_assertions.rb	2025-06-18 03:10:37.000000000 +0000
@@ -70,6 +70,7 @@ module Rails
             message ||= "Expected: #{expected}\nActual: #{actual}"
             assert_not compare_doms(expected_dom, actual_dom, strict), message
           end
+          alias_method :refute_dom_equal, :assert_dom_not_equal
 
           protected
             def compare_doms(expected, actual, strict)
diff -pruN 2.2.0-1/lib/rails/dom/testing/assertions/selector_assertions/html_selector.rb 2.3.0-1/lib/rails/dom/testing/assertions/selector_assertions/html_selector.rb
--- 2.2.0-1/lib/rails/dom/testing/assertions/selector_assertions/html_selector.rb	2024-01-12 18:08:49.000000000 +0000
+++ 2.3.0-1/lib/rails/dom/testing/assertions/selector_assertions/html_selector.rb	2025-06-18 03:10:37.000000000 +0000
@@ -14,11 +14,11 @@ module Rails
 
             include Minitest::Assertions
 
-            def initialize(values, previous_selection = nil, &root_fallback)
+            def initialize(values, previous_selection = nil, refute: false, &root_fallback)
               @values = values
               @root = extract_root(previous_selection, root_fallback)
               extract_selectors
-              @tests = extract_equality_tests
+              @tests = extract_equality_tests(refute)
               @message = @values.shift
 
               if @message.is_a?(Hash)
@@ -51,14 +51,16 @@ module Rails
 
                 content_mismatch = nil
                 text_matches = tests.has_key?(:text)
+                html_matches = tests.has_key?(:html)
                 regex_matching = match_with.is_a?(Regexp)
 
                 remaining = matches.reject do |match|
                   # Preserve markup with to_s for html elements
-                  content = text_matches ? match.text : match.children.to_s
+                  content = text_matches ? match.text : match.inner_html
 
                   content.strip! unless NO_STRIP.include?(match.name)
                   content.delete_prefix!("\n") if text_matches && match.name == "textarea"
+                  collapse_html_whitespace!(content) unless NO_STRIP.include?(match.name) || html_matches
 
                   next if regex_matching ? (content =~ match_with) : (content == match_with)
                   content_mismatch ||= diff(match_with, content)
@@ -97,7 +99,7 @@ module Rails
                 @selector     = context.substitute!(selector, @values)
               end
 
-              def extract_equality_tests
+              def extract_equality_tests(refute)
                 comparisons = {}
                 case comparator = @values.shift
                 when Hash
@@ -113,7 +115,16 @@ module Rails
                   comparisons[:count] = 0
                 when NilClass, TrueClass
                   comparisons[:minimum] = 1
-                else raise ArgumentError, "I don't understand what you're trying to match"
+                else
+                  raise ArgumentError, "I don't understand what you're trying to match"
+                end
+
+                if refute
+                  if comparisons[:count] || (comparisons[:minimum] && !comparator.nil?) || comparisons[:maximum]
+                    raise ArgumentError, "Cannot use true, false, Integer, Range, :count, :minimum and :maximum when asserting that a selector does not match"
+                  end
+
+                  comparisons[:count] = 0
                 end
 
                 # By default we're looking for at least one match.
@@ -122,8 +133,19 @@ module Rails
                 else
                   comparisons[:minimum] ||= 1
                 end
+
+                if comparisons[:minimum] && comparisons[:maximum] && comparisons[:minimum] > comparisons[:maximum]
+                  raise ArgumentError, "Range begin or :minimum cannot be greater than Range end or :maximum"
+                end
+
+                @strict = comparisons[:strict]
+
                 comparisons
               end
+
+              def collapse_html_whitespace!(text)
+                text.gsub!(/\s+/, " ")
+              end
           end
         end
       end
diff -pruN 2.2.0-1/lib/rails/dom/testing/assertions/selector_assertions.rb 2.3.0-1/lib/rails/dom/testing/assertions/selector_assertions.rb
--- 2.2.0-1/lib/rails/dom/testing/assertions/selector_assertions.rb	2024-01-12 18:08:49.000000000 +0000
+++ 2.3.0-1/lib/rails/dom/testing/assertions/selector_assertions.rb	2025-06-18 03:10:37.000000000 +0000
@@ -164,20 +164,53 @@ module Rails
             @selected ||= nil
 
             selector = HTMLSelector.new(args, @selected) { nodeset document_root_element }
+            dom_assertions(selector, &block)
+          end
+          alias_method :assert_select, :assert_dom
 
+          # The negated form of +assert_dom+.
+          #
+          # === Equality Tests
+          #
+          # Supports the same equality tests as +assert_dom+ except for:
+          # * <tt>true</tt>
+          # * <tt>false</tt>
+          # * <tt>Integer</tt>
+          # * <tt>Range</tt>
+          # * <tt>:count</tt>
+          # * <tt>:minimum</tt>
+          # * <tt>:maximum</tt>
+          def assert_not_dom(*args, &block)
+            @selected ||= nil
+
+            selector = HTMLSelector.new(args, @selected, refute: true) { nodeset document_root_element }
+            dom_assertions(selector, &block)
+          end
+          alias_method :refute_dom, :assert_not_dom
+          alias_method :assert_not_select, :assert_not_dom
+          alias_method :refute_select, :assert_not_dom
+
+          private def dom_assertions(selector, &block)
             if selector.selecting_no_body?
               assert true
               return
             end
 
+            count, max = selector.tests.slice(:count, :maximum).values
+
             selector.select.tap do |matches|
               assert_size_match!(matches.size, selector.tests,
                 selector.css_selector, selector.message)
 
-              nest_selection(matches, &block) if block_given? && !matches.empty?
+              if block_given?
+                if count&.zero? || max&.zero?
+                  raise ArgumentError, "Cannot be called with a block when asserting that a selector does not match"
+                end
+
+                nest_selection(matches, &block) unless matches.empty?
+              end
             end
           end
-          alias_method :assert_select, :assert_dom
 
           # Extracts the content of an element, treats it as encoded HTML and runs
           # nested assertion on it.
diff -pruN 2.2.0-1/lib/rails/dom/testing/version.rb 2.3.0-1/lib/rails/dom/testing/version.rb
--- 2.2.0-1/lib/rails/dom/testing/version.rb	2024-01-12 18:08:49.000000000 +0000
+++ 2.3.0-1/lib/rails/dom/testing/version.rb	2025-06-18 03:10:37.000000000 +0000
@@ -3,7 +3,7 @@
 module Rails
   module Dom
     module Testing
-      VERSION = "2.2.0"
+      VERSION = "2.3.0"
     end
   end
 end
diff -pruN 2.2.0-1/rails-dom-testing.gemspec 2.3.0-1/rails-dom-testing.gemspec
--- 2.2.0-1/rails-dom-testing.gemspec	2024-01-12 18:08:49.000000000 +0000
+++ 2.3.0-1/rails-dom-testing.gemspec	2025-06-18 03:10:37.000000000 +0000
@@ -2,16 +2,16 @@
 # This file has been automatically generated by gem2tgz #
 #########################################################
 # -*- encoding: utf-8 -*-
-# stub: rails-dom-testing 2.2.0 ruby lib
+# stub: rails-dom-testing 2.3.0 ruby lib
 
 Gem::Specification.new do |s|
   s.name = "rails-dom-testing".freeze
-  s.version = "2.2.0"
+  s.version = "2.3.0"
 
   s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
   s.require_paths = ["lib".freeze]
   s.authors = ["Rafael Mendon\u00E7a Fran\u00E7a".freeze, "Kasper Timm Hansen".freeze]
-  s.date = "2023-08-03"
+  s.date = "1980-01-02"
   s.description = "This gem can compare doms and assert certain elements exists in doms using Nokogiri.".freeze
   s.email = ["rafaelmfranca@gmail.com".freeze, "kaspth@gmail.com".freeze]
   s.files = ["MIT-LICENSE".freeze, "README.md".freeze, "lib/rails-dom-testing.rb".freeze, "lib/rails/dom/testing.rb".freeze, "lib/rails/dom/testing/assertions.rb".freeze, "lib/rails/dom/testing/assertions/dom_assertions.rb".freeze, "lib/rails/dom/testing/assertions/selector_assertions.rb".freeze, "lib/rails/dom/testing/assertions/selector_assertions/html_selector.rb".freeze, "lib/rails/dom/testing/assertions/selector_assertions/substitution_context.rb".freeze, "lib/rails/dom/testing/railtie.rb".freeze, "lib/rails/dom/testing/version.rb".freeze, "test/dom_assertions_test.rb".freeze, "test/parser_selection_test.rb".freeze, "test/selector_assertions_test.rb".freeze, "test/test_helper.rb".freeze]
diff -pruN 2.2.0-1/test/selector_assertions_test.rb 2.3.0-1/test/selector_assertions_test.rb
--- 2.2.0-1/test/selector_assertions_test.rb	2024-01-12 18:08:49.000000000 +0000
+++ 2.3.0-1/test/selector_assertions_test.rb	2025-06-18 03:10:37.000000000 +0000
@@ -16,7 +16,7 @@ class AssertSelectTest < ActiveSupport::
   end
 
   #
-  # Test assert select.
+  # Test assert_select.
   #
 
   def test_assert_select
@@ -122,6 +122,19 @@ class AssertSelectTest < ActiveSupport::
     end
   end
 
+  def test_zero_counts_with_block
+    render_html "<div>foo</div>"
+
+    errors = [
+      assert_raises(ArgumentError) { assert_select("p", false) { nil } },
+      assert_raises(ArgumentError) { assert_select("p", 0) { nil } },
+      assert_raises(ArgumentError) { assert_select("p", count: 0) { nil } },
+      assert_raises(ArgumentError) { assert_select("p", 0..0) { nil } },
+      assert_raises(ArgumentError) { assert_select("p", minimum: 0, maximum: 0) { nil } }
+    ]
+    assert_equal ["Cannot be called with a block when asserting that a selector does not match"], errors.map(&:message).uniq
+  end
+
   def test_substitution_values
     render_html '<div id="1">foo</div><div id="2">foo</div>'
     assert_select "div:match('id', ?)", /\d+/ do |elements|
@@ -210,6 +223,115 @@ class AssertSelectTest < ActiveSupport::
     end
   end
 
+  def test_assert_select_with_invalid_range
+    render_html "<div>foo</div>"
+    error = assert_raises(ArgumentError) { assert_select("div", 2..1) { nil } }
+    assert_equal "Range begin or :minimum cannot be greater than Range end or :maximum", error.message
+  end
+
+  def test_assert_select_with_invalid_minimum_and_maximum
+    render_html "<div>foo</div>"
+    error = assert_raises(ArgumentError) { assert_select("div", maximum: 0) { nil } }
+    assert_equal "Range begin or :minimum cannot be greater than Range end or :maximum", error.message
+    error = assert_raises(ArgumentError) { assert_select("div", minimum: 2, maximum: 1) { nil } }
+    assert_equal "Range begin or :minimum cannot be greater than Range end or :maximum", error.message
+  end
+
+  def test_assert_select_text_equality_collapses_whitespace
+    render_html "<p>Some\n   line-broken\n   text</p>"
+
+    assert_nothing_raised do
+      assert_select "p", {
+        text: "Some line-broken text",
+      }, "Whitespace was not collapsed from text"
+    end
+
+    render_html "<p>Some<br><br>line-broken<br><br>text</p>"
+
+    assert_nothing_raised do
+      assert_select "p", {
+        text: "Someline-brokentext",
+      }, "<br> was not removed from text"
+    end
+  end
+
+  def test_assert_select_html_equality_respects_whitespace
+    render_html "<p>Some\n   line-broken\n   text</p>"
+
+    assert_nothing_raised do
+      assert_select "p", {
+        html: "Some\n   line-broken\n   text",
+      }, "Whitespace was collapsed from html"
+    end
+
+    render_html "<p>Some<br><br>line-broken<br><br>text</p>"
+
+    assert_nothing_raised do
+      assert_select "p", {
+        html: "Some<br><br>line-broken<br><br>text",
+      }, "<br> was removed from html"
+    end
+  end
+
+  #
+  # Test assert_not_select.
+  #
+
+  def test_assert_not_select
+    render_html '<div id="1"></div>'
+    assert_not_select "p"
+    assert_failure(/Expected exactly 0 elements matching "div", found 1/) { assert_not_select "div" }
+    assert_failure(/Expected exactly 0 elements matching "div#1", found 1/) { assert_not_select "div#1" }
+  end
+
+  def test_assert_not_select_with_true
+    render_html '<div id="1"></div>'
+    error = assert_raises(ArgumentError) { assert_not_select "div", true }
+    assert_equal "Cannot use true, false, Integer, Range, :count, :minimum and :maximum when asserting that a selector does not match", error.message
+  end
+
+  def test_assert_not_select_with_false
+    render_html '<div id="1"></div>'
+    error = assert_raises(ArgumentError) { assert_not_select "div", false }
+    assert_equal "Cannot use true, false, Integer, Range, :count, :minimum and :maximum when asserting that a selector does not match", error.message
+  end
+
+  def test_assert_not_select_with_integer
+    render_html '<div id="1"></div>'
+    error = assert_raises(ArgumentError) { assert_not_select "div", 1 }
+    assert_equal "Cannot use true, false, Integer, Range, :count, :minimum and :maximum when asserting that a selector does not match", error.message
+  end
+
+  def test_assert_not_select_with_range
+    render_html '<div id="1"></div>'
+    error = assert_raises(ArgumentError) { assert_not_select "div", 1..5 }
+    assert_equal "Cannot use true, false, Integer, Range, :count, :minimum and :maximum when asserting that a selector does not match", error.message
+  end
+
+  def test_assert_not_select_with_count
+    render_html '<div id="1"></div>'
+    error = assert_raises(ArgumentError) { assert_not_select "div", count: 1 }
+    assert_equal "Cannot use true, false, Integer, Range, :count, :minimum and :maximum when asserting that a selector does not match", error.message
+  end
+
+  def test_assert_not_select_with_minimum
+    render_html '<div id="1"></div>'
+    error = assert_raises(ArgumentError) { assert_not_select "div", minimum: 1 }
+    assert_equal "Cannot use true, false, Integer, Range, :count, :minimum and :maximum when asserting that a selector does not match", error.message
+  end
+
+  def test_assert_not_select_with_maximum
+    render_html '<div id="1"></div>'
+    error = assert_raises(ArgumentError) { assert_not_select "div", maximum: 1 }
+    assert_equal "Cannot use true, false, Integer, Range, :count, :minimum and :maximum when asserting that a selector does not match", error.message
+  end
+
+  def test_assert_not_select_with_block
+    render_html "<div>foo</div>"
+    error = assert_raises(ArgumentError) { assert_not_select("p") { nil } }
+    assert_equal "Cannot be called with a block when asserting that a selector does not match", error.message
+  end
+
   #
   # Test css_select.
   #
