Repository: lsaffre/sphinxfeed (language: python)

Issue:
Unit test improvements
Following up from #2 and #5. Here are some changes I would like to make to the unit tests:

* Improve test coverage:
  * Test both rst and markdown input
  * Test both RSS and Atom output
* Get tests fully working in CI:
  * Currently, tests compare the full build output, which changes slightly when dependency versions change (Sphinx, docutils, etc.). This could be improved by checking only the main feed contents and ignoring minor variations like formatting, whitespace, `lastBuildDate`, etc.
  * It looks like the tests are also checking HTML output. Do you think we could skip that and only check the feed XML output?


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 01d8db9..384297a 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -27,9 +27,7 @@ jobs:
           pip install -U pip setuptools
           pip install -e .[dev]
       - name: Run tests
-        run: pytest -v tests || diff tmp/ tests/docs1/expected && exit 1
-        # Remove this line once tests are updated to work in CI:
-        continue-on-error: true
+        run: pytest -vs tests
 
   # For git tags, build and publish package to PyPI
   publish:
diff --git a/README.rst b/README.rst
index 228627b..fc02581 100644
--- a/README.rst
+++ b/README.rst
@@ -106,6 +106,11 @@ Run the test suite::
 
   $ pytest
 
+Generate an HTML test coverage report::
+
+  $ pytest --cov-report=html
+  $ python -m webbrowser test-reports/index.html
+
 Release a new version to PyPI::
 
   $ hatch version micro
diff --git a/pyproject.toml b/pyproject.toml
index af4a278..20e403f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -48,6 +48,7 @@ dependencies = [
 dev = [
     "hatch",
     "pytest",
+    "pytest-cov",
 ]
 
 [project.urls]
@@ -65,3 +66,15 @@ only-include = ["sphinxfeed.py"]
 
 [tool.hatch.build]
 dev-mode-dirs = ["."]
+
+# Coverage report config: by default, show condensed terminal output
+[tool.coverage.run]
+branch = true
+source = ['.']
+omit = ['tests/*', 'tasks.py']
+
+[tool.coverage.html]
+directory = 'test-reports'
+
+[tool.pytest.ini_options]
+addopts = "--cov --cov-report=term"


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..e7d9b65
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,39 @@
+"""Pytest config for testing with SphinxTestApp
+
+Notes on testing setup:
+* A Sphinx test app is provided via `app` fixture from `sphinx.testing.fixtures`.
+* The `sources` dir contains source files and config to use for tests
+    * Set via the `rootdir` fixture
+* A subdirectory to use for a specific test can be set by a pytest marker:
+    * `@pytest.mark.sphinx("html", testroot="...")
+    * This subdirectory must contain a conf.py and source files
+* The `outputs` dir contains expected output files
+* Test build output is located under `/tmp/pytest*`
+"""
+
+import shutil
+from pathlib import Path
+
+import pytest
+
+collect_ignore = ["sources", "outputs"]
+pytest_plugins = "sphinx.testing.fixtures"
+
+OUTPUT_DIR = Path(__file__).parent.resolve() / "outputs"
+SOURCE_DIR = Path(__file__).parent.resolve() / "sources"
+
+
+@pytest.fixture(scope="session")
+def rootdir():
+    """This fixture overrides the root directory used by SphinxTestApp. Also patches in a
+    Path.copytree() method for compatibility with sphinx.testing.path.path in older Sphinx versions.
+    """
+
+    class PatchedPath(type(Path())):
+        def __new__(cls, *args):
+            return super().__new__(cls, *args)
+
+        def copytree(src, dest):
+            shutil.copytree(src, dest, symlinks=True)
+
+    yield PatchedPath(SOURCE_DIR)
diff --git a/tests/docs1/.static/dummy b/tests/docs1/.static/dummy
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/docs1/conf.py b/tests/docs1/conf.py
deleted file mode 100644
index 6e8a0dc..0000000
--- a/tests/docs1/conf.py
+++ /dev/null
@@ -1,220 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# build configuration file, created by
-# sphinx-quickstart on Mon Nov 10 16:33:38 2008.
-#
-# This file is execfile()d with the current directory set to its containing dir.
-#
-# The contents of this file are pickled, so don't put values in the namespace
-# that aren't pickleable (module imports are okay, they're removed automatically).
-#
-# All configuration values have a default value; values that are commented out
-# serve to show the default value.
-
-# from __future__ import unicode_literals
-# from rstgen.sphinxconf import configure
-
-extensions = []
-templates_path = []
-
-# configure(globals())
-
-# If your extensions are in another directory, add it here. If the directory
-# is relative to the documentation root, use os.path.abspath to make it
-# absolute, like shown here.
-#sys.path.append(os.path.abspath('some/directory'))
-
-# General configuration
-# ---------------------
-
-# Add any paths that contain templates here, relative to this directory.
-# templates_path = ['.templates']
-
-# The suffix of source filenames.
-source_suffix = '.rst'
-
-# The encoding of source files.
-#source_encoding = 'utf-8-sig'
-
-# The master toctree document.
-master_doc = 'index'
-
-# General substitutions.
-project = "First sphinxfeed tester"
-copyright = '2018 Joe Doe'
-
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The short X.Y version.
-version = ''
-# The full version, including alpha/beta/rc tags.
-release = ''
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-language = 'en'
-
-# There are two options for replacing |today|: either, you set today to some
-# non-false value, then it is used:
-#today = ''
-# Else, today_fmt is used as the format for a strftime call.
-# today_fmt = '%d.%B.%Y'
-
-# Note 20100703 : unused_docs and exclude_trees  replaced by exclude_patterns
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-exclude_patterns = ['.build']
-
-# List of documents that shouldn't be included in the build.
-#unused_docs = []
-
-# List of directories, relative to source directories, that shouldn't be searched
-# for source files.
-exclude_trees = ['old', 'include', '.build']
-
-# The reST default role (used for this markup: `text`) to use for all documents.
-#default_role = None
-
-# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
-
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
-#add_module_names = True
-
-# If true, sectionauthor and moduleauthor directives will be shown in the
-# output. They are ignored by default.
-#show_authors = False
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
-
-# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
-
-# -- Options for HTML output ---------------------------------------------------
-
-# The theme to use for HTML and HTML Help pages.  See the documentation for
-# a list of builtin themes.
-# html_theme = 'default'
-
-# Theme options are theme-specific and customize the look and feel of a theme
-# further.  For a list of options available for each theme, see the
-# documentation.
-#html_theme_options = {}
-
-# Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
-
-# The name for this set of Sphinx documents.  If None, it defaults to
-# "<project> v<release> documentation".
-html_title = "Joe's website"
-
-# A shorter title for the navigation bar.  Default is the same as html_title.
-html_short_title = u"Home"
-
-# The name of an image file (relative to this directory) to place at the top
-# of the sidebar.
-# html_logo = "logo2.jpg"
-# html_logo = "logo.png"
-
-# The name of an image file (within the static path) to use as favicon of the
-# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
-# pixels large.
-#html_favicon = None
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['.static']
-
-# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
-# using the given strftime format.
-html_last_updated_fmt = '%Y-%m-%d'
-#~ html_last_updated_fmt = '%b %d, %Y'
-
-# If true, SmartyPants will be used to convert quotes and dashes to
-# typographically correct entities.
-#html_use_smartypants = True
-
-# Additional templates that should be rendered to pages, maps page names to
-# template names.
-#html_additional_pages = {}
-
-# If false, no module index is generated.
-html_use_modindex = True
-
-# If false, no index is generated.
-html_use_index = True
-
-# If true, the index is split into individual pages for each letter.
-#html_split_index = False
-
-# If true, the reST sources are included in the HTML build as _sources/<name>.
-#html_copy_source = True
-
-# If true, an OpenSearch description file will be output, and all pages will
-# contain a <link> tag referring to it.  The value of this option must be the
-# base URL from which the finished HTML is served.
-#html_use_opensearch = ''
-
-# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = ''
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = 'saffre-rumma'
-
-# Options for LaTeX output
-# ------------------------
-
-# The paper size ('letter' or 'a4').
-#latex_paper_size = 'letter'
-
-# The font size ('10pt', '11pt' or '12pt').
-#latex_font_size = '10pt'
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title, author, document class [howto/manual]).
-latex_documents = [
-    ('index', 'saffre-rumma.tex', u'saffre-rumma', u'saffre-rumma', 'manual'),
-]
-
-# The name of an image file (relative to this directory) to place at the top of
-# the title page.
-#latex_logo = None
-
-# For "manual" documents, if this is true, then toplevel headings are parts,
-# not chapters.
-#latex_use_parts = False
-
-# Additional stuff for the LaTeX preamble.
-#latex_preamble = ''
-
-# Documents to append as an appendix to all manuals.
-#latex_appendices = []
-
-# If false, no module index is generated.
-#latex_use_modindex = True
-
-# html_sidebars = {
-#     '**': ['globaltoc.html', 'searchbox.html', 'links.html'],
-# }
-
-# html_theme = "classic"
-# html_theme_options = dict(collapsiblesidebar=True, externalrefs=True)
-
-# html_theme = "bizstyle"
-# html_theme_options = dict(collapsiblesidebar=True, externalrefs=True)
-
-# extensions += ['yasfb']
-# extensions += ['sphinxcontrib.feed']
-extensions += ['sphinxfeed']
-# NB : not the public sphinxfeed but my extended version
-feed_base_url = 'http://news.example.com'
-feed_author = 'Joe Dow'
-feed_title = "Joe's blog"
-feed_field_name = 'date'
-feed_description = "Joe's blog"
diff --git a/tests/docs1/expected/first.html b/tests/docs1/expected/first.html
deleted file mode 100644
index 714a76a..0000000
--- a/tests/docs1/expected/first.html
+++ /dev/null
@@ -1,112 +0,0 @@
-<!DOCTYPE html>
-
-<html lang="en" data-content_root="./">
-  <head>
-    <meta charset="utf-8" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="viewport" content="width=device-width, initial-scale=1" />
-
-    <title>First day &#8212; Joe&#39;s website</title>
-    <link rel="stylesheet" type="text/css" href="_static/pygments.css?v=fa44fd50" />
-    <link rel="stylesheet" type="text/css" href="_static/alabaster.css?v=12dfc556" />
-    <script src="_static/documentation_options.js?v=5929fcd5"></script>
-    <script src="_static/doctools.js?v=9a2dae69"></script>
-    <script src="_static/sphinx_highlight.js?v=dc90522c"></script>
-    <link rel="index" title="Index" href="genindex.html" />
-    <link rel="search" title="Search" href="search.html" />
-    <link rel="next" title="Second day" href="second.html" />
-    <link rel="prev" title="Joe’s blog" href="index.html" />
-   
-  <link rel="stylesheet" href="_static/custom.css" type="text/css" />
-  
-
-  
-  
-
-  </head><body>
-  
-
-    <div class="document">
-      <div class="documentwrapper">
-        <div class="bodywrapper">
-          
-
-          <div class="body" role="main">
-            
-  <section id="first-day">
-<h1>First day<a class="headerlink" href="#first-day" title="Link to this heading">¶</a></h1>
-<p>Mul seitse pruuti on,
-neid kõiki armastan
-ja iga päev neist üht mina külastan.
-Ikka Emma esmaspäev, …</p>
-</section>
-
-
-          </div>
-          
-        </div>
-      </div>
-      <div class="sphinxsidebar" role="navigation" aria-label="Main">
-        <div class="sphinxsidebarwrapper">
-<h1 class="logo"><a href="index.html">First sphinxfeed tester</a></h1>
-
-
-
-
-
-
-
-
-<h3>Navigation</h3>
-<ul class="current">
-<li class="toctree-l1 current"><a class="current reference internal" href="#">First day</a></li>
-<li class="toctree-l1"><a class="reference internal" href="second.html">Second day</a></li>
-</ul>
-
-<div class="relations">
-<h3>Related Topics</h3>
-<ul>
-  <li><a href="index.html">Documentation overview</a><ul>
-      <li>Previous: <a href="index.html" title="previous chapter">Joe’s blog</a></li>
-      <li>Next: <a href="second.html" title="next chapter">Second day</a></li>
-  </ul></li>
-</ul>
-</div>
-<search id="searchbox" style="display: none" role="search">
-  <h3 id="searchlabel">Quick search</h3>
-    <div class="searchformwrapper">
-    <form class="search" action="search.html" method="get">
-      <input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
-      <input type="submit" value="Go" />
-    </form>
-    </div>
-</search>
-<script>document.getElementById('searchbox').style.display = "block"</script>
-
-
-
-
-
-
-
-
-        </div>
-      </div>
-      <div class="clearer"></div>
-    </div>
-    <div class="footer">
-      &#169;2018 Joe Doe.
-      
-      |
-      Powered by <a href="https://www.sphinx-doc.org/">Sphinx 7.4.5</a>
-      &amp; <a href="https://alabaster.readthedocs.io">Alabaster 0.7.16</a>
-      
-      |
-      <a href="_sources/first.rst.txt"
-          rel="nofollow">Page source</a>
-    </div>
-
-    
-
-    
-  </body>
-</html>
\ No newline at end of file
diff --git a/tests/docs1/expected/genindex.html b/tests/docs1/expected/genindex.html
deleted file mode 100644
index 23b6037..0000000
--- a/tests/docs1/expected/genindex.html
+++ /dev/null
@@ -1,103 +0,0 @@
-<!DOCTYPE html>
-
-<html lang="en" data-content_root="./">
-  <head>
-    <meta charset="utf-8" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>Index &#8212; Joe&#39;s website</title>
-    <link rel="stylesheet" type="text/css" href="_static/pygments.css?v=fa44fd50" />
-    <link rel="stylesheet" type="text/css" href="_static/alabaster.css?v=12dfc556" />
-    <script src="_static/documentation_options.js?v=5929fcd5"></script>
-    <script src="_static/doctools.js?v=9a2dae69"></script>
-    <script src="_static/sphinx_highlight.js?v=dc90522c"></script>
-    <link rel="index" title="Index" href="#" />
-    <link rel="search" title="Search" href="search.html" />
-   
-  <link rel="stylesheet" href="_static/custom.css" type="text/css" />
-  
-
-  
-  
-
-  </head><body>
-  
-
-    <div class="document">
-      <div class="documentwrapper">
-        <div class="bodywrapper">
-          
-
-          <div class="body" role="main">
-            
-
-<h1 id="index">Index</h1>
-
-<div class="genindex-jumpbox">
- 
-</div>
-
-
-          </div>
-          
-        </div>
-      </div>
-      <div class="sphinxsidebar" role="navigation" aria-label="Main">
-        <div class="sphinxsidebarwrapper">
-<h1 class="logo"><a href="index.html">First sphinxfeed tester</a></h1>
-
-
-
-
-
-
-
-
-<h3>Navigation</h3>
-<ul>
-<li class="toctree-l1"><a class="reference internal" href="first.html">First day</a></li>
-<li class="toctree-l1"><a class="reference internal" href="second.html">Second day</a></li>
-</ul>
-
-<div class="relations">
-<h3>Related Topics</h3>
-<ul>
-  <li><a href="index.html">Documentation overview</a><ul>
-  </ul></li>
-</ul>
-</div>
-<search id="searchbox" style="display: none" role="search">
-  <h3 id="searchlabel">Quick search</h3>
-    <div class="searchformwrapper">
-    <form class="search" action="search.html" method="get">
-      <input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
-      <input type="submit" value="Go" />
-    </form>
-    </div>
-</search>
-<script>document.getElementById('searchbox').style.display = "block"</script>
-
-
-
-
-
-
-
-
-        </div>
-      </div>
-      <div class="clearer"></div>
-    </div>
-    <div class="footer">
-      &#169;2018 Joe Doe.
-      
-      |
-      Powered by <a href="https://www.sphinx-doc.org/">Sphinx 7.4.5</a>
-      &amp; <a href="https://alabaster.readthedocs.io">Alabaster 0.7.16</a>
-      
-    </div>
-
-    
-
-    
-  </body>
-</html>
\ No newline at end of file
diff --git a/tests/docs1/expected/index.html b/tests/docs1/expected/index.html
deleted file mode 100644
index 7a8490a..0000000
--- a/tests/docs1/expected/index.html
+++ /dev/null
@@ -1,115 +0,0 @@
-<!DOCTYPE html>
-
-<html lang="en" data-content_root="./">
-  <head>
-    <meta charset="utf-8" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="viewport" content="width=device-width, initial-scale=1" />
-
-    <title>Joe’s blog &#8212; Joe&#39;s website</title>
-    <link rel="stylesheet" type="text/css" href="_static/pygments.css?v=fa44fd50" />
-    <link rel="stylesheet" type="text/css" href="_static/alabaster.css?v=12dfc556" />
-    <script src="_static/documentation_options.js?v=5929fcd5"></script>
-    <script src="_static/doctools.js?v=9a2dae69"></script>
-    <script src="_static/sphinx_highlight.js?v=dc90522c"></script>
-    <link rel="index" title="Index" href="genindex.html" />
-    <link rel="search" title="Search" href="search.html" />
-    <link rel="next" title="First day" href="first.html" />
-   
-  <link rel="stylesheet" href="_static/custom.css" type="text/css" />
-  
-
-  
-  
-
-  </head><body>
-  
-
-    <div class="document">
-      <div class="documentwrapper">
-        <div class="bodywrapper">
-          
-
-          <div class="body" role="main">
-            
-  <section id="joe-s-blog">
-<h1>Joe’s blog<a class="headerlink" href="#joe-s-blog" title="Link to this heading">¶</a></h1>
-<section id="sitemap">
-<h2>Sitemap<a class="headerlink" href="#sitemap" title="Link to this heading">¶</a></h2>
-<div class="toctree-wrapper compound">
-<ul>
-<li class="toctree-l1"><a class="reference internal" href="first.html">First day</a></li>
-<li class="toctree-l1"><a class="reference internal" href="second.html">Second day</a></li>
-</ul>
-</div>
-</section>
-</section>
-
-
-          </div>
-          
-        </div>
-      </div>
-      <div class="sphinxsidebar" role="navigation" aria-label="Main">
-        <div class="sphinxsidebarwrapper">
-<h1 class="logo"><a href="#">First sphinxfeed tester</a></h1>
-
-
-
-
-
-
-
-
-<h3>Navigation</h3>
-<ul>
-<li class="toctree-l1"><a class="reference internal" href="first.html">First day</a></li>
-<li class="toctree-l1"><a class="reference internal" href="second.html">Second day</a></li>
-</ul>
-
-<div class="relations">
-<h3>Related Topics</h3>
-<ul>
-  <li><a href="#">Documentation overview</a><ul>
-      <li>Next: <a href="first.html" title="next chapter">First day</a></li>
-  </ul></li>
-</ul>
-</div>
-<search id="searchbox" style="display: none" role="search">
-  <h3 id="searchlabel">Quick search</h3>
-    <div class="searchformwrapper">
-    <form class="search" action="search.html" method="get">
-      <input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
-      <input type="submit" value="Go" />
-    </form>
-    </div>
-</search>
-<script>document.getElementById('searchbox').style.display = "block"</script>
-
-
-
-
-
-
-
-
-        </div>
-      </div>
-      <div class="clearer"></div>
-    </div>
-    <div class="footer">
-      &#169;2018 Joe Doe.
-      
-      |
-      Powered by <a href="https://www.sphinx-doc.org/">Sphinx 7.4.5</a>
-      &amp; <a href="https://alabaster.readthedocs.io">Alabaster 0.7.16</a>
-      
-      |
-      <a href="_sources/index.rst.txt"
-          rel="nofollow">Page source</a>
-    </div>
-
-    
-
-    
-  </body>
-</html>
\ No newline at end of file
diff --git a/tests/docs1/expected/rss.xml b/tests/docs1/expected/rss.xml
deleted file mode 100644
index 653189e..0000000
--- a/tests/docs1/expected/rss.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-<?xml version='1.0' encoding='UTF-8'?>
-<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"><channel><title>First sphinxfeed tester</title><link>http://news.example.com</link><description>Joe's blog</description><copyright>2018 Joe Doe</copyright><docs>http://www.rssboard.org/rss-specification</docs><generator>python-feedgen</generator><language>en</language><lastBuildDate>Wed, 17 Jul 2024 16:31:50 +0000</lastBuildDate></channel></rss>
\ No newline at end of file
diff --git a/tests/docs1/expected/search.html b/tests/docs1/expected/search.html
deleted file mode 100644
index 66d507e..0000000
--- a/tests/docs1/expected/search.html
+++ /dev/null
@@ -1,120 +0,0 @@
-<!DOCTYPE html>
-
-<html lang="en" data-content_root="./">
-  <head>
-    <meta charset="utf-8" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>Search &#8212; Joe&#39;s website</title>
-    <link rel="stylesheet" type="text/css" href="_static/pygments.css?v=fa44fd50" />
-    <link rel="stylesheet" type="text/css" href="_static/alabaster.css?v=12dfc556" />
-    
-    <script src="_static/documentation_options.js?v=5929fcd5"></script>
-    <script src="_static/doctools.js?v=9a2dae69"></script>
-    <script src="_static/sphinx_highlight.js?v=dc90522c"></script>
-    <script src="_static/searchtools.js"></script>
-    <script src="_static/language_data.js"></script>
-    <link rel="index" title="Index" href="genindex.html" />
-    <link rel="search" title="Search" href="#" />
-    <script src="searchindex.js" defer="defer"></script>
-    <meta name="robots" content="noindex" />
-    
-   
-  <link rel="stylesheet" href="_static/custom.css" type="text/css" />
-  
-
-  
-  
-
-
-  </head><body>
-  
-
-    <div class="document">
-      <div class="documentwrapper">
-        <div class="bodywrapper">
-          
-
-          <div class="body" role="main">
-            
-  <h1 id="search-documentation">Search</h1>
-  
-  <noscript>
-  <div class="admonition warning">
-  <p>
-    Please activate JavaScript to enable the search
-    functionality.
-  </p>
-  </div>
-  </noscript>
-  
-  
-  <p>
-    Searching for multiple words only shows matches that contain
-    all words.
-  </p>
-  
-  
-  <form action="" method="get">
-    <input type="text" name="q" aria-labelledby="search-documentation" value="" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
-    <input type="submit" value="search" />
-    <span id="search-progress" style="padding-left: 10px"></span>
-  </form>
-  
-  
-  <div id="search-results"></div>
-  
-
-          </div>
-          
-        </div>
-      </div>
-      <div class="sphinxsidebar" role="navigation" aria-label="Main">
-        <div class="sphinxsidebarwrapper">
-<h1 class="logo"><a href="index.html">First sphinxfeed tester</a></h1>
-
-
-
-
-
-
-
-
-<h3>Navigation</h3>
-<ul>
-<li class="toctree-l1"><a class="reference internal" href="first.html">First day</a></li>
-<li class="toctree-l1"><a class="reference internal" href="second.html">Second day</a></li>
-</ul>
-
-<div class="relations">
-<h3>Related Topics</h3>
-<ul>
-  <li><a href="index.html">Documentation overview</a><ul>
-  </ul></li>
-</ul>
-</div>
-
-
-
-
-
-
-
-
-        </div>
-      </div>
-      <div class="clearer"></div>
-    </div>
-    <div class="footer">
-      &#169;2018 Joe Doe.
-      
-      |
-      Powered by <a href="https://www.sphinx-doc.org/">Sphinx 7.4.5</a>
-      &amp; <a href="https://alabaster.readthedocs.io">Alabaster 0.7.16</a>
-      
-    </div>
-
-    
-
-    
-  </body>
-</html>
\ No newline at end of file
diff --git a/tests/docs1/expected/searchindex.js b/tests/docs1/expected/searchindex.js
deleted file mode 100644
index 4bf4412..0000000
--- a/tests/docs1/expected/searchindex.js
+++ /dev/null
@@ -1,1 +0,0 @@
-Search.setIndex({"alltitles": {"First day": [[0, null]], "Future post (draft)": [[1, null]], "Joe\u2019s blog": [[2, null]], "Second day": [[3, null]], "Sitemap": [[2, "sitemap"]]}, "docnames": ["first", "future", "index", "second"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2}, "filenames": ["first.rst", "future.rst", "index.rst", "second.rst"], "indexentries": {}, "objects": {}, "objnames": {}, "objtypes": {}, "terms": {"armastan": [0, 3], "dai": 2, "emma": [0, 3], "esmasp\u00e4ev": [0, 3], "first": 2, "iga": [0, 3], "ikka": [0, 3], "ja": [0, 3], "k\u00f5iki": [0, 3], "k\u00fclastan": [0, 3], "mina": [0, 3], "mul": [0, 3], "neid": [0, 3], "neist": [0, 3], "pruuti": [0, 3], "publish": 1, "p\u00e4ev": [0, 3], "second": 2, "seits": [0, 3], "should": 1, "teisip\u00e4ev": 3, "teres": 3, "thi": 1, "yet": 1, "\u00fcht": [0, 3]}, "titles": ["First day", "Future post (draft)", "Joe\u2019s blog", "Second day"], "titleterms": {"": 2, "blog": 2, "dai": [0, 3], "draft": 1, "first": 0, "futur": 1, "joe": 2, "post": 1, "second": 3, "sitemap": 2}})
\ No newline at end of file
diff --git a/tests/docs1/expected/second.html b/tests/docs1/expected/second.html
deleted file mode 100644
index 77c7767..0000000
--- a/tests/docs1/expected/second.html
+++ /dev/null
@@ -1,111 +0,0 @@
-<!DOCTYPE html>
-
-<html lang="en" data-content_root="./">
-  <head>
-    <meta charset="utf-8" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="viewport" content="width=device-width, initial-scale=1" />
-
-    <title>Second day &#8212; Joe&#39;s website</title>
-    <link rel="stylesheet" type="text/css" href="_static/pygments.css?v=fa44fd50" />
-    <link rel="stylesheet" type="text/css" href="_static/alabaster.css?v=12dfc556" />
-    <script src="_static/documentation_options.js?v=5929fcd5"></script>
-    <script src="_static/doctools.js?v=9a2dae69"></script>
-    <script src="_static/sphinx_highlight.js?v=dc90522c"></script>
-    <link rel="index" title="Index" href="genindex.html" />
-    <link rel="search" title="Search" href="search.html" />
-    <link rel="prev" title="First day" href="first.html" />
-   
-  <link rel="stylesheet" href="_static/custom.css" type="text/css" />
-  
-
-  
-  
-
-  </head><body>
-  
-
-    <div class="document">
-      <div class="documentwrapper">
-        <div class="bodywrapper">
-          
-
-          <div class="body" role="main">
-            
-  <section id="second-day">
-<h1>Second day<a class="headerlink" href="#second-day" title="Link to this heading">¶</a></h1>
-<p>Mul seitse pruuti on,
-neid kõiki armastan
-ja iga päev neist üht mina külastan.
-Ikka Emma esmaspäev,
-Terese teisipäev,</p>
-</section>
-
-
-          </div>
-          
-        </div>
-      </div>
-      <div class="sphinxsidebar" role="navigation" aria-label="Main">
-        <div class="sphinxsidebarwrapper">
-<h1 class="logo"><a href="index.html">First sphinxfeed tester</a></h1>
-
-
-
-
-
-
-
-
-<h3>Navigation</h3>
-<ul class="current">
-<li class="toctree-l1"><a class="reference internal" href="first.html">First day</a></li>
-<li class="toctree-l1 current"><a class="current reference internal" href="#">Second day</a></li>
-</ul>
-
-<div class="relations">
-<h3>Related Topics</h3>
-<ul>
-  <li><a href="index.html">Documentation overview</a><ul>
-      <li>Previous: <a href="first.html" title="previous chapter">First day</a></li>
-  </ul></li>
-</ul>
-</div>
-<search id="searchbox" style="display: none" role="search">
-  <h3 id="searchlabel">Quick search</h3>
-    <div class="searchformwrapper">
-    <form class="search" action="search.html" method="get">
-      <input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
-      <input type="submit" value="Go" />
-    </form>
-    </div>
-</search>
-<script>document.getElementById('searchbox').style.display = "block"</script>
-
-
-
-
-
-
-
-
-        </div>
-      </div>
-      <div class="clearer"></div>
-    </div>
-    <div class="footer">
-      &#169;2018 Joe Doe.
-      
-      |
-      Powered by <a href="https://www.sphinx-doc.org/">Sphinx 7.4.5</a>
-      &amp; <a href="https://alabaster.readthedocs.io">Alabaster 0.7.16</a>
-      
-      |
-      <a href="_sources/second.rst.txt"
-          rel="nofollow">Page source</a>
-    </div>
-
-    
-
-    
-  </body>
-</html>
\ No newline at end of file
diff --git a/tests/outputs/atom.xml b/tests/outputs/atom.xml
new file mode 100644
index 0000000..d11ece9
--- /dev/null
+++ b/tests/outputs/atom.xml
@@ -0,0 +1,49 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
+    <id>http://news.example.com</id>
+    <title>First sphinxfeed tester</title>
+    <updated>2024-07-21T18:58:02.430522+00:00</updated>
+    <author>
+        <name>Joe Dow</name>
+    </author>
+    <link href="http://news.example.com"/>
+    <generator uri="https://lkiesow.github.io/python-feedgen" version="1.0.0">python-feedgen</generator>
+    <rights>2018 Joe Doe</rights>
+    <subtitle>Joe's blog</subtitle>
+    <entry>
+        <id>http://news.example.com/second</id>
+        <title>Second day</title>
+        <updated>2024-07-21T18:58:02.552860+00:00</updated>
+        <author>
+            <name>Joe</name>
+        </author>
+        <content>
+            &lt;section id="second-day"&gt;
+            &lt;h1&gt;Second day&lt;a class="headerlink" href="#second-day" title="Link to this heading"&gt;¶&lt;/a&gt;&lt;/h1&gt;
+            &lt;p&gt;Mul seitse pruuti on,
+            neid kõiki armastan
+            ja iga päev neist üht mina külastan.
+            Ikka Emma esmaspäev,
+            Terese teisipäev,&lt;/p&gt;
+            &lt;/section&gt;
+        </content>
+        <link href="http://news.example.com/second"/>
+        <published>2018-03-12T23:30:00+00:00</published>
+    </entry>
+    <entry>
+        <id>http://news.example.com/first</id>
+        <title>First day</title>
+        <updated>2024-07-21T18:58:02.488103+00:00</updated>
+        <content>
+            &lt;section id="first-day"&gt;
+            &lt;h1&gt;First day&lt;a class="headerlink" href="#first-day" title="Link to this heading"&gt;¶&lt;/a&gt;&lt;/h1&gt;
+            &lt;p&gt;Mul seitse pruuti on,
+            neid kõiki armastan
+            ja iga päev neist üht mina külastan.
+            Ikka Emma esmaspäev, …&lt;/p&gt;
+            &lt;/section&gt;
+        </content>
+        <link href="http://news.example.com/first"/>
+        <published>2018-03-11T00:00:00+00:00</published>
+    </entry>
+</feed>
\ No newline at end of file
diff --git a/tests/outputs/rss.xml b/tests/outputs/rss.xml
new file mode 100644
index 0000000..5faf564
--- /dev/null
+++ b/tests/outputs/rss.xml
@@ -0,0 +1,43 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<rss xmlns:atom="http://www.w3.org/2005/Atom"
+    xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
+    <channel>
+        <title>First sphinxfeed tester</title>
+        <link>http://news.example.com</link>
+        <description>Joe's blog</description>
+        <copyright>2018 Joe Doe</copyright>
+        <docs>http://www.rssboard.org/rss-specification</docs>
+        <generator>python-feedgen</generator>
+        <language>en</language>
+        <lastBuildDate>Sun, 21 Jul 2024 18:18:18 +0000</lastBuildDate>
+        <item>
+            <title>Second day</title>
+            <link>http://news.example.com/second.html</link>
+            <description>
+            &lt;section id="second-day"&gt;
+            &lt;h1&gt;Second day&lt;a class="headerlink" href="#second-day" title="Link to this heading"&gt;¶&lt;/a&gt;&lt;/h1&gt;
+            &lt;p&gt;Mul seitse pruuti on,
+            neid kõiki armastan
+            ja iga päev neist üht mina külastan.
+            Ikka Emma esmaspäev,
+            Terese teisipäev,&lt;/p&gt;
+            &lt;/section&gt;
+            </description>
+            <pubDate>Mon, 12 Mar 2018 23:30:00 +0000</pubDate>
+        </item>
+        <item>
+            <title>First day</title>
+            <link>http://news.example.com/first.html</link>
+            <description>
+            &lt;section id="first-day"&gt;
+            &lt;h1&gt;First day&lt;a class="headerlink" href="#first-day" title="Link to this heading"&gt;¶&lt;/a&gt;&lt;/h1&gt;
+            &lt;p&gt;Mul seitse pruuti on,
+            neid kõiki armastan
+            ja iga päev neist üht mina külastan.
+            Ikka Emma esmaspäev, …&lt;/p&gt;
+            &lt;/section&gt;
+            </description>
+            <pubDate>Sun, 11 Mar 2018 00:00:00 +0000</pubDate>
+        </item>
+    </channel>
+</rss>
\ No newline at end of file
diff --git a/tests/sources/test-atom/conf.py b/tests/sources/test-atom/conf.py
new file mode 100644
index 0000000..8183fd4
--- /dev/null
+++ b/tests/sources/test-atom/conf.py
@@ -0,0 +1,20 @@
+# General sphinx config
+source_suffix = '.rst'
+master_doc = 'index'
+project = "First sphinxfeed tester"
+copyright = '2018 Joe Doe'
+language = 'en'
+html_title = "Joe's website"
+html_short_title = u"Home"
+html_last_updated_fmt = '%Y-%m-%d'
+use_dirhtml = True
+
+# Sphinxfeed config
+extensions = ['sphinxfeed']
+feed_base_url = 'http://news.example.com'
+feed_author = 'Joe Dow'
+feed_title = "Joe's blog"
+feed_field_name = 'date'
+feed_description = "Joe's blog"
+feed_filename = 'atom.xml'
+feed_use_atom = True
\ No newline at end of file
diff --git a/tests/sources/test-atom/empty.rst b/tests/sources/test-atom/empty.rst
new file mode 100644
index 0000000..7ccaed8
--- /dev/null
+++ b/tests/sources/test-atom/empty.rst
@@ -0,0 +1,4 @@
+:date: 2018-03-11
+:orphan:
+
+.. missing title; should be ignored
diff --git a/tests/docs1/first.rst b/tests/sources/test-atom/first.rst
similarity index 90%
rename from tests/docs1/first.rst
rename to tests/sources/test-atom/first.rst
index e72dd5b..c6a1a55 100644
--- a/tests/docs1/first.rst
+++ b/tests/sources/test-atom/first.rst
@@ -1,4 +1,5 @@
 :date: 2018-03-11
+:category: test
 
 =========
 First day
diff --git a/tests/docs1/future.rst b/tests/sources/test-atom/future.rst
similarity index 100%
rename from tests/docs1/future.rst
rename to tests/sources/test-atom/future.rst
diff --git a/tests/docs1/index.rst b/tests/sources/test-atom/index.rst
similarity index 90%
rename from tests/docs1/index.rst
rename to tests/sources/test-atom/index.rst
index 8223d2d..3d1fd13 100644
--- a/tests/docs1/index.rst
+++ b/tests/sources/test-atom/index.rst
@@ -11,3 +11,4 @@ Sitemap
 
    first
    second
+   future
diff --git a/tests/docs1/second.rst b/tests/sources/test-atom/second.rst
similarity index 88%
rename from tests/docs1/second.rst
rename to tests/sources/test-atom/second.rst
index eb2fc11..f66c746 100644
--- a/tests/docs1/second.rst
+++ b/tests/sources/test-atom/second.rst
@@ -1,10 +1,11 @@
 :date: March 12 2018, 11:30 PM UTC
 :author: Joe
+:tags: poetry, unit-test
 
 ==========
 Second day
 ==========
-.. tags:: poetry, unit-test
+
 
 Mul seitse pruuti on,
 neid kõiki armastan
diff --git a/tests/sources/test-rss/conf.py b/tests/sources/test-rss/conf.py
new file mode 100644
index 0000000..4bbb3ad
--- /dev/null
+++ b/tests/sources/test-rss/conf.py
@@ -0,0 +1,19 @@
+# General sphinx config
+source_suffix = '.rst'
+master_doc = 'index'
+project = "First sphinxfeed tester"
+copyright = '2018 Joe Doe'
+language = 'en'
+html_title = "Joe's website"
+html_short_title = u"Home"
+html_last_updated_fmt = '%Y-%m-%d'
+
+# Sphinxfeed config
+extensions = ['sphinxfeed']
+feed_base_url = 'http://news.example.com'
+feed_author = 'Joe Dow'
+feed_title = "Joe's blog"
+feed_field_name = 'date'
+feed_description = "Joe's blog"
+feed_filename = 'rss.xml'
+feed_use_atom = False
\ No newline at end of file
diff --git a/tests/sources/test-rss/empty.rst b/tests/sources/test-rss/empty.rst
new file mode 100644
index 0000000..cb890a6
--- /dev/null
+++ b/tests/sources/test-rss/empty.rst
@@ -0,0 +1,4 @@
+:date: 2018-03-11
+:orphan:
+
+.. missing body; should be ignored
diff --git a/tests/sources/test-rss/first.rst b/tests/sources/test-rss/first.rst
new file mode 100644
index 0000000..c6a1a55
--- /dev/null
+++ b/tests/sources/test-rss/first.rst
@@ -0,0 +1,11 @@
+:date: 2018-03-11
+:category: test
+
+=========
+First day
+=========
+
+Mul seitse pruuti on,
+neid kõiki armastan
+ja iga päev neist üht mina külastan.
+Ikka Emma esmaspäev, ...
diff --git a/tests/sources/test-rss/future.rst b/tests/sources/test-rss/future.rst
new file mode 100644
index 0000000..cb36fd6
--- /dev/null
+++ b/tests/sources/test-rss/future.rst
@@ -0,0 +1,7 @@
+:date: 3030-03-03T03:03:03Z
+
+===================
+Future post (draft)
+===================
+
+This post should not be published yet!
\ No newline at end of file
diff --git a/tests/sources/test-rss/index.rst b/tests/sources/test-rss/index.rst
new file mode 100644
index 0000000..3d1fd13
--- /dev/null
+++ b/tests/sources/test-rss/index.rst
@@ -0,0 +1,14 @@
+==========
+Joe's blog
+==========
+
+
+Sitemap
+-------
+
+.. toctree::
+   :maxdepth: 1
+
+   first
+   second
+   future
diff --git a/tests/sources/test-rss/second.rst b/tests/sources/test-rss/second.rst
new file mode 100644
index 0000000..f66c746
--- /dev/null
+++ b/tests/sources/test-rss/second.rst
@@ -0,0 +1,14 @@
+:date: March 12 2018, 11:30 PM UTC
+:author: Joe
+:tags: poetry, unit-test
+
+==========
+Second day
+==========
+
+
+Mul seitse pruuti on,
+neid kõiki armastan
+ja iga päev neist üht mina külastan.
+Ikka Emma esmaspäev,
+Terese teisipäev,
diff --git a/tests/test_sphinxfeed.py b/tests/test_sphinxfeed.py
index 815e69a..7a800d2 100644
--- a/tests/test_sphinxfeed.py
+++ b/tests/test_sphinxfeed.py
@@ -1,42 +1,137 @@
-# -*- coding: UTF-8 -*-
 # Copyright 2018-2024 Rumma & Ko Ltd
 """
-Run a sphinx-build and then check whether the generated files (in
-`tmp`) are the same as in `expected`.
+Runs sphinx builds using SphinxTestApp to generate RSS and Atom feeds, and compares them to expected
+output one element at a time. Shows detailed output on failure.
+"""
+from io import StringIO
+from pathlib import Path
+from textwrap import dedent
+from unittest.mock import patch
+from xml.etree import ElementTree
+from xml.etree.ElementTree import Element
 
-The tests fail when the Sphinx version has changed.  In that case::
+import pytest
+from dateutil.tz import UTC
+from sphinx.testing.util import SphinxTestApp
 
-  $ diff tmp/ tests/docs1/expected
+from tests.conftest import OUTPUT_DIR
 
-and if there is no other changes, update the expected files::
+RSS_ITEM_ATTRIBUTES = ["title", "link", "description", "pubDate"]
+RSS_META_ATTRIBUTES = [
+    "copyright",
+    "description",
+    "docs",
+    "generator",
+    "language",
+    "link",
+    "title",
+]
 
-  $ cp tmp/*.html tmp/*.js tests/docs1/expected
+ATOM_SCHEMA = "http://www.w3.org/2005/Atom"
+ATOM_META_ATTRIBUTES = [
+    "id",
+    "author/name",
+    "generator",
+    "link",
+    "rights",
+    "title",
+]
+ATOM_ITEM_ATTRIBUTES = [
+    "id",
+    "content",
+    "link",
+    "published",
+    "title",
+]
 
-"""
 
-import filecmp
-from unittest import TestCase
-import subprocess
+@pytest.mark.sphinx("html", testroot="rss")
+@patch("sphinxfeed.tzlocal", return_value=UTC)
+def test_build_rss(mock_tzlocal, app: SphinxTestApp, status: StringIO):
+    app.build(force_all=True)
+    assert "build succeeded" in status.getvalue()
+
+    build_dir = Path(app.srcdir) / "_build" / "html"
+    _compare_rss_feeds((build_dir / "rss.xml"), (OUTPUT_DIR / "rss.xml"))
+
+
+def _compare_rss_feeds(file_1: Path, file_2: Path):
+    """Compare XML contents of two RSS feeds, ignoring formatting, whitespace, and build date."""
+    feed_contents_1 = _parse_xml(file_1).find("channel")
+    feed_contents_2 = _parse_xml(file_2).find("channel")
+
+    # compare metadata
+    for attr in RSS_META_ATTRIBUTES:
+        _compare_attrs(attr, feed_contents_1, feed_contents_2)
+
+    # Compare all feed items
+    feed_items_1 = feed_contents_1.findall("item")
+    feed_items_2 = feed_contents_2.findall("item")
+    assert len(feed_items_1) == len(feed_items_2)
+    for item_1, item_2 in zip(feed_items_1, feed_items_2):
+        for attr in RSS_ITEM_ATTRIBUTES:
+            _compare_attrs(attr, item_1, item_2)
+
+
+@pytest.mark.sphinx("html", testroot="atom")
+@patch("sphinxfeed.tzlocal", return_value=UTC)
+def test_build_atom(mock_tzlocal, app: SphinxTestApp, status: StringIO):
+    app.build(force_all=True)
+    assert "build succeeded" in status.getvalue()
+
+    build_dir = Path(app.srcdir) / "_build" / "html"
+    _compare_atom_feeds((build_dir / "atom.xml"), (OUTPUT_DIR / "atom.xml"))
+
+
+def _compare_atom_feeds(file_1: Path, file_2: Path):
+    """Compare XML contents of two Atom feeds, ignoring formatting, whitespace, and build date."""
+    feed_contents_1 = _parse_xml(file_1)
+    feed_contents_2 = _parse_xml(file_2)
+
+    # compare metadata
+    for attr in ATOM_META_ATTRIBUTES:
+        _compare_attrs(attr, feed_contents_1, feed_contents_2, atom=True)
+
+    # Compare all feed items
+    feed_items_1 = feed_contents_1.findall(f"{{{ATOM_SCHEMA}}}entry")
+    feed_items_2 = feed_contents_2.findall(f"{{{ATOM_SCHEMA}}}entry")
+    assert len(feed_items_1) == len(feed_items_2)
+    for entry_1, entry_2 in zip(feed_items_1, feed_items_2):
+        for attr in ATOM_ITEM_ATTRIBUTES:
+            _compare_attrs(attr, entry_1, entry_2, atom=True)
+
 
+def _parse_xml(file: Path):
+    return ElementTree.fromstring(file.read_text())
 
-class AllTests(TestCase):
 
-    def test_all(self):
-        args = ['sphinx-build']
-        args += ["-b"]
-        args += ["html"]
-        args += ["tests/docs1"]
-        args += ["tmp"]
-        subprocess.check_output(args, stderr=subprocess.STDOUT)
+def _compare_attrs(attr: str, e1: Element, e2: Element, atom: bool = False):
+    """Compare attribute values in two XML elements, handle variations in formatting, and print
+    comparison to show on test failure.
+    """
+    print(f"[{attr}]:")
 
-        common = [
-            "index.html", "first.html", "search.html", "genindex.html",
-            "searchindex.js"
-        ]
-        # common.append("rss.xml")
+    # For Atom feeds, we need to append the Atom schema to the attribute name
+    if atom:
+        if attr == "author/name":
+            attr = f"{{{ATOM_SCHEMA}}}author/{{{ATOM_SCHEMA}}}name"
+        else:
+            attr = f"{{{ATOM_SCHEMA}}}{attr}"
 
-        match, mismatch, errors = filecmp.cmpfiles("tests/docs1/expected",
-                                                   "tmp", common)
+    # Handle one or both values missing
+    if (val_1 := e1.find(attr)) is None or (val_2 := e2.find(attr)) is None:
+        raise ValueError(f"Attribute {attr} missing")
+    # Handle link attribute
+    if atom and attr.endswith("link"):
+        text_1 = val_1.attrib["href"]
+        text_2 = val_2.attrib["href"]
+    # Handle whitespace differences in HTML content
+    else:
+        text_1 = dedent(val_1.text).strip()
+        text_2 = dedent(val_2.text).strip()
+    # Handle different phrasing in Sphinx <=7.1
+    text_1 = text_1.replace('Permalink', 'Link')
+    text_2 = text_2.replace('Permalink', 'Link')
 
-        self.assertEqual(mismatch, [])
-        self.assertEqual(match, common)
+    print(f"  expected: {text_1}\n  actual: {text_2}")
+    assert text_1 == text_2