aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorLibravatarUnit 193 <unit193@unit193.net>2021-02-03 19:17:00 -0500
committerLibravatarUnit 193 <unit193@unit193.net>2021-02-03 19:17:00 -0500
commit3a0d66f07b112b6d2bdc2b57bbf717a89a351ce6 (patch)
treea7cf56282e54f05785243bc1e903d6594f2c06ba /tests
parent787b97a4cb24330b36f11297c6d3a7a473a907d0 (diff)
New upstream version 8.1.2.upstream/8.1.2
Diffstat (limited to 'tests')
-rw-r--r--tests/README.rst105
-rw-r--r--tests/__init__.py2
-rw-r--r--tests/base.py259
-rw-r--r--tests/conftest.py24
-rw-r--r--tests/data/1-nolinks.rst1
-rw-r--r--tests/data/metadata_extractors/f-html-1-compiler.html7
-rw-r--r--tests/data/metadata_extractors/f-ipynb-1-compiler.ipynb31
-rw-r--r--tests/data/metadata_extractors/f-markdown-1-compiler.md7
-rw-r--r--tests/data/metadata_extractors/f-markdown-1-nikola.md13
-rw-r--r--tests/data/metadata_extractors/f-markdown-2-nikola.md2
-rw-r--r--tests/data/metadata_extractors/f-markdown-2-nikola.meta7
-rw-r--r--tests/data/metadata_extractors/f-rest-1-compiler.rst9
-rw-r--r--tests/data/metadata_extractors/f-rest-1-nikola.rst11
-rw-r--r--tests/data/metadata_extractors/f-rest-1-toml.rst8
-rw-r--r--tests/data/metadata_extractors/f-rest-1-yaml.rst8
-rw-r--r--tests/data/metadata_extractors/f-rest-2-nikola.meta7
-rw-r--r--tests/data/metadata_extractors/f-rest-2-nikola.rst2
-rw-r--r--tests/data/metadata_extractors/f-rest-2-toml.meta6
-rw-r--r--tests/data/metadata_extractors/f-rest-2-toml.rst2
-rw-r--r--tests/data/metadata_extractors/f-rest-2-yaml.meta6
-rw-r--r--tests/data/metadata_extractors/f-rest-2-yaml.rst2
-rw-r--r--tests/data/rss-2_0.xsd (renamed from tests/rss-2_0.xsd)0
-rw-r--r--tests/data/test_config/conf.py30
-rw-r--r--tests/data/test_config/config.with+illegal(module)name.characters.py6
-rw-r--r--tests/data/test_config/prod.py6
-rw-r--r--tests/data/translated_titles/conf.py694
-rw-r--r--tests/data/translated_titles/pages/1.pl.txt (renamed from tests/data/translated_titles/stories/1.pl.txt)0
-rw-r--r--tests/data/translated_titles/pages/1.txt (renamed from tests/data/translated_titles/stories/1.txt)0
-rw-r--r--tests/data/wordpress_import/wordpress_export_example.xml (renamed from tests/wordpress_export_example.xml)0
-rw-r--r--tests/data/wordpress_import/wordpress_qtranslate_item_modernized.xml30
-rw-r--r--tests/data/wordpress_import/wordpress_qtranslate_item_raw_export.xml30
-rw-r--r--tests/data/wordpress_import/wordpress_unicode_export.xml (renamed from tests/wordpress_unicode_export.xml)0
-rw-r--r--tests/helper.py131
-rw-r--r--tests/import_wordpress_and_build_workflow.py43
-rw-r--r--tests/integration/__init__.py12
-rw-r--r--tests/integration/conftest.py74
-rw-r--r--tests/integration/helper.py56
-rw-r--r--tests/integration/test_archive_full.py44
-rw-r--r--tests/integration/test_archive_per_day.py36
-rw-r--r--tests/integration/test_archive_per_month.py36
-rw-r--r--tests/integration/test_building_in_subdir.py32
-rw-r--r--tests/integration/test_category_destpath.py88
-rw-r--r--tests/integration/test_check_absolute_subfolder.py50
-rw-r--r--tests/integration/test_check_failure.py47
-rw-r--r--tests/integration/test_check_full_path_subfolder.py35
-rw-r--r--tests/integration/test_demo_build.py43
-rw-r--r--tests/integration/test_empty_build.py54
-rw-r--r--tests/integration/test_future_post.py109
-rw-r--r--tests/integration/test_page_index_normal_urls.py238
-rw-r--r--tests/integration/test_page_index_pretty_urls.py57
-rw-r--r--tests/integration/test_redirection.py106
-rw-r--r--tests/integration/test_relative_links.py60
-rw-r--r--tests/integration/test_relative_links_with_pages_in_root.py65
-rw-r--r--tests/integration/test_repeated_posts_setting.py36
-rw-r--r--tests/integration/test_translated_content.py62
-rw-r--r--tests/integration/test_translated_content_secondary_language.py40
-rw-r--r--tests/integration/test_translation_patterns.py55
-rw-r--r--tests/integration/test_wordpress_import.py59
-rw-r--r--tests/test_command_import_wordpress.py757
-rw-r--r--tests/test_command_import_wordpress_translation.py144
-rw-r--r--tests/test_command_init.py228
-rw-r--r--tests/test_commands.py15
-rw-r--r--tests/test_compile_markdown.py158
-rw-r--r--tests/test_config.py63
-rw-r--r--tests/test_integration.py600
-rw-r--r--tests/test_locale.py459
-rw-r--r--tests/test_metadata_extractors.py225
-rw-r--r--tests/test_path_handlers.py114
-rw-r--r--tests/test_plugin_importing.py19
-rw-r--r--tests/test_plugins.py16
-rw-r--r--tests/test_rss_feeds.py308
-rw-r--r--tests/test_rst_compiler.py468
-rw-r--r--tests/test_scheduling.py255
-rw-r--r--tests/test_shortcodes.py200
-rw-r--r--tests/test_slugify.py120
-rw-r--r--tests/test_task_scale_images.py126
-rw-r--r--tests/test_template_shortcodes.py78
-rw-r--r--tests/test_utils.py916
78 files changed, 4769 insertions, 3483 deletions
diff --git a/tests/README.rst b/tests/README.rst
deleted file mode 100644
index cdf5c8a..0000000
--- a/tests/README.rst
+++ /dev/null
@@ -1,105 +0,0 @@
-.. title: The Nikola Test Suite
-.. slug: tests
-.. date: 2012/03/30 23:00
-
-The Nikola Test Suite
-=====================
-
-Nikola, like many software projects, has a test suite. There are over 100
-tests.
-
-Tests (in alphabetical order)
------------------------------
-
-* ``test_command_import_wordpress`` tests the WordPress importer for
- Nikola.
-* ``test_command_init`` checks whether new sites are created properly via the
- ``init`` command.
-* ``test_compile_markdown`` exercises the Markdown compiler plugin of Nikola.
-* ``test_integration`` are used to validate that sites actually build.
-* ``test_locale`` tests the locale support of Nikola.
-* ``test_plugin_importing`` checks three basic plugins to know whether they
- get imported properly.
-* ``test_rss_feeds`` asserts that RSS created by Nikola is sane.
-* ``test_rst_compiler`` exercises the reStructuredText compiler plugin of
- Nikola.
-* ``test_scheduling`` performs tests on post scheduling rules.
-* ``test_utils`` test various Nikola utilities.
-
-Requirements to run the tests
------------------------------
-
-You need:
-
-* ``pip install -r requirements-tests.txt``
-* a few minutes’ time
-* appropriate locale settings
-
-How to set the locale for Nikola tests?
----------------------------------------
-
-For testing nikola needs to specify two languages, each one with a supported locale. By default, the test suite uses ``en`` and ``pl`` as languages, and their respective default locale for them.
-
-The choice of Polish is due to having one locale to generate instead of 20 (Spanish) and you can happily ignore it — just set the language–locale pairs by exporting two shell variables, for example::
-
- export NIKOLA_LOCALE_DEFAULT=en,en_US.utf8
- export NIKOLA_LOCALE_OTHER=pl,pl_PL.utf8
-
-In Windows that would be::
-
- set NIKOLA_LOCALE_DEFAULT=en,English
- set NIKOLA_LOCALE_OTHER=pl,Polish
-
-Replace the part before the comma with a Nikola translation selector (see ``nikola/conf.py.in`` for details), and the part after the comma with an *installed* glibc locale.
-
-To check if the desired locale is supported in your host you can, in a python console::
-
- import locale
- locale.setlocale(locale.LC_ALL, 'locale_name')
- # for example, 'en_US.utf8' (posix) 'English' (windows)
- # if it does not traceback, then python can use that locale
-
-Alternatively, if you have some disk space to spare, you can install
-the two default locales. Here is how to do that in Ubuntu::
-
- sudo apt-get install language-pack-en language-pack-pl
-
-
-How to execute the tests
-------------------------
-
-The command to execute tests is::
-
- doit coverage
-
-Note that Travis does not use this command — and as such, differences between the two may appear.
-
-In Windows you want to drop the doctests parts, they fail over trivial differences in OS details.
-
-It is also recommended to run ``nikola help`` to see if Nikola actually
-works.
-
-If you are committing code, make sure to run ``flake8 --ignore=E501 .`` to see if you comply with the PEP 8 style guide and do not have basic code mistakes (we ignore the 79-characters-per-line rule).
-
-In windows ignore the two flake8 diagnostics about messages_sl_si.py , they are artifacts of (symlinks + git + windows).
-
-
-Travis CI
----------
-
-We also run our tests on `Travis CI <https://travis-ci.org/>`_.
-You can check the `current build status <https://travis-ci.org/getnikola/nikola>`_ there.
-
-
-Writing tests
--------------
-
-* When adding new *.py files under tests/ , remember to include at the begining the lines::
-
- # This code is so you can run the samples without installing the package,
- # and should be before any import touching nikola, in any file under tests/
- import os
- import sys
- sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
-
- Those lines allow to run the tests without installing nikola.
diff --git a/tests/__init__.py b/tests/__init__.py
index ed1acfa..4e8ada2 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1 +1 @@
-# Tests for Nikola.
+"""Tests for Nikola."""
diff --git a/tests/base.py b/tests/base.py
deleted file mode 100644
index 14af18a..0000000
--- a/tests/base.py
+++ /dev/null
@@ -1,259 +0,0 @@
-# coding: utf8
-# Author: Rodrigo Bistolfi
-# Date: 03/2013
-
-
-""" Base class for Nikola test cases """
-
-
-__all__ = ["BaseTestCase", "cd", "LocaleSupportInTesting"]
-
-
-import os
-import sys
-
-
-from contextlib import contextmanager
-import locale
-import unittest
-
-import logbook
-
-import nikola.utils
-nikola.utils.LOGGER.handlers.append(logbook.TestHandler())
-
-from yapsy.PluginManager import PluginManager
-from nikola.plugin_categories import (
- Command,
- Task,
- LateTask,
- TemplateSystem,
- PageCompiler,
- TaskMultiplier,
- CompilerExtension,
- MarkdownExtension,
- RestExtension
-)
-
-
-if sys.version_info < (2, 7):
-
- try:
- import unittest2
- _unittest2 = True
- except ImportError:
- _unittest2 = False
-
- if _unittest2:
- BaseTestCase = unittest2.TestCase
-
- else:
-
- class BaseTestCase(unittest.TestCase):
- """ Base class for providing 2.6 compatibility """
-
- def assertIs(self, first, second, msg=None):
- self.assertTrue(first is second)
-
- def assertIsNot(self, first, second, msg=None):
- self.assertTrue(first is not second)
-
- def assertIsNone(self, expr, msg=None):
- self.assertTrue(expr is None)
-
- def assertIsNotNone(self, expr, msg=None):
- self.assertTrue(expr is not None)
-
- def assertIn(self, first, second, msg=None):
- self.assertTrue(first in second)
-
- def assertNotIn(self, first, second, msg=None):
- self.assertTrue(first not in second)
-
- def assertIsInstance(self, obj, cls, msg=None):
- self.assertTrue(isinstance(obj, cls))
-
- def assertNotIsInstance(self, obj, cls, msg=None):
- self.assertFalse(isinstance(obj, cls))
-
-
-else:
- BaseTestCase = unittest.TestCase
-
-
-@contextmanager
-def cd(path):
- old_dir = os.getcwd()
- os.chdir(path)
- yield
- os.chdir(old_dir)
-
-
-class LocaleSupportInTesting(object):
- """
- Nikola needs two pairs of valid (language, locale_n) to test multilingual sites.
-
- As languages of interest and installed OS support varies from host to host
- we allow to specify two such pairs.
-
- A valid pair complies
- 'languaje' one of the names of nikola translations ('en', 'es', ...)
- 'locale_n' is a string that python accepts to set a locale, like in
- import locale
- locale.setlocale(locale.LC_ALL, str(locale_n))
-
- You specify the custom pairs to use with two environment variables
- NIKOLA_LOCALE_DEFAULT (lang and locale to use as nikola's DEFAULT_LANG)
- NIKOLA_LOCALE_OTHER
-
- The value of the pair is lang (as in keys of Nikola's TRANSLATIONS), followed
- by coma, followed by the locale.
- """
-
- @classmethod
- def initialize(cls):
- """Determines and diagnoses the two (lang, locale) pairs to use in testing
-
- While it only needs to run once at the beginning of the testing session,
- calling multiple times is fine.
- """
- if hasattr(cls, 'langlocales'):
- return
- defaults = {
- 'posix': {
- # non-windows defaults, must be two locales suported by .travis.yml
- 'default': ("en", str("en_US.utf8")),
- 'other': ("pl", str("pl_PL.utf8")),
- },
- 'windows': {
- # windows defaults
- 'default': ("en", str("English")),
- 'other': ("pl", str("Polish")),
- },
- }
- os_id = 'windows' if sys.platform == 'win32' else 'posix'
- langlocales = {}
- for suffix in ['other', 'default']:
- try:
- envar = 'NIKOLA_LOCALE_' + suffix.upper()
- s = os.environ[envar]
- parts = s.split(',')
- lang = parts[0].strip()
- try:
- locale_n = str(parts[1].strip())
- locale.setlocale(locale.LC_ALL, locale_n)
- except Exception:
- msg = ("Environment variable {0} fails to specify a valid <lang>,<locale>." +
- "Check your syntax, check that python supports that locale in your host.")
- nikola.utils.LOGGER.error(msg.format(envar))
- sys.exit(1)
- except KeyError:
- lang, locale_n = defaults[os_id][suffix]
- langlocales[suffix] = (lang, locale_n)
- if (langlocales['default'][0] == langlocales['other'][0] or
- langlocales['default'][1] == langlocales['other'][1]): # NOQA
- # the mix of defaults and enviro is not good
- msg = ('Locales for testing should differ in lang and locale, else ' +
- 'the test would we weak. Check your environment settings for ' +
- 'NIKOLA_LOCALE_DEFAULT and NIKOLA_LOCALE_OTHER')
- nikola.utils.LOGGER.error(msg)
- setattr(cls, 'langlocales', langlocales)
-
- @classmethod
- def initialize_locales_for_testing(cls, variant):
- """initializes nikola.utils.LocaleBorg"""
- if not hasattr(cls, 'langlocales'):
- cls.initialize()
- default_lang = cls.langlocales['default'][0]
- locales = {}
- locales[default_lang] = cls.langlocales['default'][1]
- if variant == 'unilingual':
- pass
- elif variant == 'bilingual':
- locales[cls.langlocales['other'][0]] = cls.langlocales['other'][1]
- else:
- raise ValueError('Unknown locale variant')
- nikola.utils.LocaleBorg.reset()
- nikola.utils.LocaleBorg.initialize(locales, default_lang)
-
-
-class FakePost(object):
-
- def __init__(self, title, slug):
- self._title = title
- self._slug = slug
- self._meta = {'slug': slug}
-
- def title(self):
- return self._title
-
- def meta(self, key):
- return self._meta[key]
-
- def permalink(self):
- return '/posts/' + self._slug
-
-
-class FakeSite(object):
- def __init__(self):
- self.template_system = self
- self.invariant = False
- self.config = {
- 'DISABLED_PLUGINS': [],
- 'EXTRA_PLUGINS': [],
- 'DEFAULT_LANG': 'en',
- 'MARKDOWN_EXTENSIONS': ['fenced_code', 'codehilite'],
- 'TRANSLATIONS_PATTERN': '{path}.{lang}.{ext}',
- 'LISTINGS_FOLDERS': {'listings': 'listings'},
- }
- self.EXTRA_PLUGINS = self.config['EXTRA_PLUGINS']
- self.plugin_manager = PluginManager(categories_filter={
- "Command": Command,
- "Task": Task,
- "LateTask": LateTask,
- "TemplateSystem": TemplateSystem,
- "PageCompiler": PageCompiler,
- "TaskMultiplier": TaskMultiplier,
- "CompilerExtension": CompilerExtension,
- "MarkdownExtension": MarkdownExtension,
- "RestExtension": RestExtension
- })
- self.loghandlers = nikola.utils.STDERR_HANDLER # TODO remove on v8
- self.plugin_manager.setPluginInfoExtension('plugin')
- if sys.version_info[0] == 3:
- places = [
- os.path.join(os.path.dirname(nikola.utils.__file__), 'plugins'),
- ]
- else:
- places = [
- os.path.join(os.path.dirname(nikola.utils.__file__), nikola.utils.sys_encode('plugins')),
- ]
- self.plugin_manager.setPluginPlaces(places)
- self.plugin_manager.collectPlugins()
- self.compiler_extensions = self._activate_plugins_of_category("CompilerExtension")
-
- self.timeline = [
- FakePost(title='Fake post',
- slug='fake-post')
- ]
- self.debug = True
- self.rst_transforms = []
- # This is to make plugin initialization happy
- self.template_system = self
- self.name = 'mako'
-
- def _activate_plugins_of_category(self, category):
- """Activate all the plugins of a given category and return them."""
- # this code duplicated in nikola/nikola.py
- plugins = []
- for plugin_info in self.plugin_manager.getPluginsOfCategory(category):
- if plugin_info.name in self.config.get('DISABLED_PLUGINS'):
- self.plugin_manager.removePluginFromCategory(plugin_info, category)
- else:
- self.plugin_manager.activatePluginByName(plugin_info.name)
- plugin_info.plugin_object.set_site(self)
- plugins.append(plugin_info)
- return plugins
-
- def render_template(self, name, _, context):
- return('<img src="IMG.jpg">')
diff --git a/tests/conftest.py b/tests/conftest.py
index fbb09c8..56fe8bd 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,8 +1,24 @@
import os
import pytest
-@pytest.yield_fixture(autouse=True)
+
+@pytest.fixture(autouse=True)
def ensure_chdir():
- x = os.getcwd()
- yield
- os.chdir(x)
+ old_dir = os.getcwd()
+ try:
+ yield
+ finally:
+ os.chdir(old_dir)
+
+
+@pytest.fixture(scope="module")
+def test_dir():
+ """
+ Absolute path to the directory with the tests.
+ """
+ return os.path.abspath(os.path.dirname(__file__))
+
+
+@pytest.fixture(scope="session")
+def default_locale() -> str:
+ return os.environ.get("NIKOLA_LOCALE_DEFAULT", "en")
diff --git a/tests/data/1-nolinks.rst b/tests/data/1-nolinks.rst
index 7f168fb..a644793 100644
--- a/tests/data/1-nolinks.rst
+++ b/tests/data/1-nolinks.rst
@@ -1,6 +1,7 @@
.. title: Welcome to Nikola
.. slug: welcome-to-nikola
.. date: 2012-03-30 23:00:00 UTC-03:00
+.. updated: 2018-08-10 06:54:00Z
.. tags: nikola, python, demo, blog
.. author: Roberto Alsina
.. link: https://getnikola.com/
diff --git a/tests/data/metadata_extractors/f-html-1-compiler.html b/tests/data/metadata_extractors/f-html-1-compiler.html
new file mode 100644
index 0000000..5e95c47
--- /dev/null
+++ b/tests/data/metadata_extractors/f-html-1-compiler.html
@@ -0,0 +1,7 @@
+<meta name="title" content="T: HTML, 1, compiler">
+<meta name="slug" content="s-html-1-compiler">
+<meta name="date" content="2017-07-01 00:00:00 UTC">
+<meta name="tags" content="meta,HTML,onefile,compiler">
+
+Content line 1.
+Content line 2. \ No newline at end of file
diff --git a/tests/data/metadata_extractors/f-ipynb-1-compiler.ipynb b/tests/data/metadata_extractors/f-ipynb-1-compiler.ipynb
new file mode 100644
index 0000000..3f6d18f
--- /dev/null
+++ b/tests/data/metadata_extractors/f-ipynb-1-compiler.ipynb
@@ -0,0 +1,31 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Content line 1.\nContent line 2."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "env": {},
+ "language": "python",
+ "name": "python3"
+ },
+ "nikola": {
+ "category": "",
+ "date": "2017-07-01 00:00:00 UTC",
+ "description": "",
+ "link": "",
+ "slug": "s-ipynb-1-compiler",
+ "tags": "meta,Jupyter Notebook,onefile,compiler",
+ "title": "T: Jupyter Notebook, 1, compiler",
+ "type": "text"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/tests/data/metadata_extractors/f-markdown-1-compiler.md b/tests/data/metadata_extractors/f-markdown-1-compiler.md
new file mode 100644
index 0000000..689bb0f
--- /dev/null
+++ b/tests/data/metadata_extractors/f-markdown-1-compiler.md
@@ -0,0 +1,7 @@
+title: T: Markdown, 1, compiler
+slug: s-markdown-1-compiler
+date: 2017-07-01 00:00:00 UTC
+tags: meta,Markdown,onefile,compiler
+
+Content line 1.
+Content line 2.
diff --git a/tests/data/metadata_extractors/f-markdown-1-nikola.md b/tests/data/metadata_extractors/f-markdown-1-nikola.md
new file mode 100644
index 0000000..5b38c20
--- /dev/null
+++ b/tests/data/metadata_extractors/f-markdown-1-nikola.md
@@ -0,0 +1,13 @@
+<!--
+.. title: T: Markdown, 1, Nikola
+.. slug: s-markdown-1-nikola
+.. date: 2017-07-01 00:00:00 UTC
+.. tags: meta,Markdown,onefile,Nikola
+.. category:
+.. link:
+.. description:
+.. type: text
+-->
+
+Content line 1.
+Content line 2.
diff --git a/tests/data/metadata_extractors/f-markdown-2-nikola.md b/tests/data/metadata_extractors/f-markdown-2-nikola.md
new file mode 100644
index 0000000..0bd667b
--- /dev/null
+++ b/tests/data/metadata_extractors/f-markdown-2-nikola.md
@@ -0,0 +1,2 @@
+Content line 1.
+Content line 2.
diff --git a/tests/data/metadata_extractors/f-markdown-2-nikola.meta b/tests/data/metadata_extractors/f-markdown-2-nikola.meta
new file mode 100644
index 0000000..c068ba6
--- /dev/null
+++ b/tests/data/metadata_extractors/f-markdown-2-nikola.meta
@@ -0,0 +1,7 @@
+.. title: T: Markdown, 2, Nikola
+.. slug: s-markdown-2-nikola
+.. date: 2017-07-01 00:00:00 UTC
+.. tags: meta,Markdown,twofile,Nikola
+.. link:
+.. description:
+.. type: text
diff --git a/tests/data/metadata_extractors/f-rest-1-compiler.rst b/tests/data/metadata_extractors/f-rest-1-compiler.rst
new file mode 100644
index 0000000..3b21c3f
--- /dev/null
+++ b/tests/data/metadata_extractors/f-rest-1-compiler.rst
@@ -0,0 +1,9 @@
+T: reST, 1, compiler
+====================
+
+:slug: s-rest-1-compiler
+:Date: 2017-07-01 00:00:00 UTC
+:tags: meta,reST,onefile,compiler
+
+Content line 1.
+Content line 2.
diff --git a/tests/data/metadata_extractors/f-rest-1-nikola.rst b/tests/data/metadata_extractors/f-rest-1-nikola.rst
new file mode 100644
index 0000000..14dede0
--- /dev/null
+++ b/tests/data/metadata_extractors/f-rest-1-nikola.rst
@@ -0,0 +1,11 @@
+.. title: T: reST, 1, Nikola
+.. slug: s-rest-1-nikola
+.. date: 2017-07-01 00:00:00 UTC
+.. tags: meta,reST,onefile,Nikola
+.. category:
+.. link:
+.. description:
+.. type: text
+
+Content line 1.
+Content line 2.
diff --git a/tests/data/metadata_extractors/f-rest-1-toml.rst b/tests/data/metadata_extractors/f-rest-1-toml.rst
new file mode 100644
index 0000000..0e2c4eb
--- /dev/null
+++ b/tests/data/metadata_extractors/f-rest-1-toml.rst
@@ -0,0 +1,8 @@
++++
+title = "T: reST, 1, TOML"
+slug = "s-rest-1-toml"
+date = "2017-07-01 00:00:00 UTC"
+tags = "meta,reST,onefile,TOML"
++++
+Content line 1.
+Content line 2.
diff --git a/tests/data/metadata_extractors/f-rest-1-yaml.rst b/tests/data/metadata_extractors/f-rest-1-yaml.rst
new file mode 100644
index 0000000..b904b35
--- /dev/null
+++ b/tests/data/metadata_extractors/f-rest-1-yaml.rst
@@ -0,0 +1,8 @@
+---
+title: "T: reST, 1, YAML"
+slug: s-rest-1-yaml
+date: "2017-07-01 00:00:00 UTC"
+tags: ["meta", "reST", "onefile", "YAML"]
+---
+Content line 1.
+Content line 2.
diff --git a/tests/data/metadata_extractors/f-rest-2-nikola.meta b/tests/data/metadata_extractors/f-rest-2-nikola.meta
new file mode 100644
index 0000000..aeb6f49
--- /dev/null
+++ b/tests/data/metadata_extractors/f-rest-2-nikola.meta
@@ -0,0 +1,7 @@
+.. title: T: reST, 2, Nikola
+.. slug: s-rest-2-nikola
+.. date: 2017-07-01 00:00:00 UTC
+.. tags: meta,reST,twofile,Nikola
+.. link:
+.. description:
+.. type: text
diff --git a/tests/data/metadata_extractors/f-rest-2-nikola.rst b/tests/data/metadata_extractors/f-rest-2-nikola.rst
new file mode 100644
index 0000000..0bd667b
--- /dev/null
+++ b/tests/data/metadata_extractors/f-rest-2-nikola.rst
@@ -0,0 +1,2 @@
+Content line 1.
+Content line 2.
diff --git a/tests/data/metadata_extractors/f-rest-2-toml.meta b/tests/data/metadata_extractors/f-rest-2-toml.meta
new file mode 100644
index 0000000..4235df9
--- /dev/null
+++ b/tests/data/metadata_extractors/f-rest-2-toml.meta
@@ -0,0 +1,6 @@
++++
+title = "T: reST, 2, TOML"
+slug = "s-rest-2-toml"
+date = "2017-07-01 00:00:00 UTC"
+tags = "meta,reST,twofile,TOML"
++++
diff --git a/tests/data/metadata_extractors/f-rest-2-toml.rst b/tests/data/metadata_extractors/f-rest-2-toml.rst
new file mode 100644
index 0000000..0bd667b
--- /dev/null
+++ b/tests/data/metadata_extractors/f-rest-2-toml.rst
@@ -0,0 +1,2 @@
+Content line 1.
+Content line 2.
diff --git a/tests/data/metadata_extractors/f-rest-2-yaml.meta b/tests/data/metadata_extractors/f-rest-2-yaml.meta
new file mode 100644
index 0000000..87d83bc
--- /dev/null
+++ b/tests/data/metadata_extractors/f-rest-2-yaml.meta
@@ -0,0 +1,6 @@
+---
+title: "T: reST, 2, YAML"
+slug: s-rest-2-yaml
+date: "2017-07-01 00:00:00 UTC"
+tags: ["meta", "reST", "twofile", "YAML"]
+---
diff --git a/tests/data/metadata_extractors/f-rest-2-yaml.rst b/tests/data/metadata_extractors/f-rest-2-yaml.rst
new file mode 100644
index 0000000..0bd667b
--- /dev/null
+++ b/tests/data/metadata_extractors/f-rest-2-yaml.rst
@@ -0,0 +1,2 @@
+Content line 1.
+Content line 2.
diff --git a/tests/rss-2_0.xsd b/tests/data/rss-2_0.xsd
index d7ddaee..d7ddaee 100644
--- a/tests/rss-2_0.xsd
+++ b/tests/data/rss-2_0.xsd
diff --git a/tests/data/test_config/conf.py b/tests/data/test_config/conf.py
new file mode 100644
index 0000000..2a2d1b8
--- /dev/null
+++ b/tests/data/test_config/conf.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+import time
+
+BLOG_AUTHOR = "Your Name"
+BLOG_TITLE = "Demo Site"
+SITE_URL = "https://example.com/"
+BLOG_EMAIL = "joe@demo.site"
+BLOG_DESCRIPTION = "This is a demo site for Nikola."
+DEFAULT_LANG = "en"
+CATEGORY_ALLOW_HIERARCHIES = False
+CATEGORY_OUTPUT_FLAT_HIERARCHY = False
+HIDDEN_CATEGORIES = []
+HIDDEN_AUTHORS = ['Guest']
+LICENSE = ""
+
+CONTENT_FOOTER_FORMATS = {
+ DEFAULT_LANG: (
+ (),
+ {
+ "email": BLOG_EMAIL,
+ "author": BLOG_AUTHOR,
+ "date": time.gmtime().tm_year,
+ "license": LICENSE
+ }
+ )
+}
+
+ADDITIONAL_METADATA = {
+ "ID": "conf"
+}
diff --git a/tests/data/test_config/config.with+illegal(module)name.characters.py b/tests/data/test_config/config.with+illegal(module)name.characters.py
new file mode 100644
index 0000000..39a8aeb
--- /dev/null
+++ b/tests/data/test_config/config.with+illegal(module)name.characters.py
@@ -0,0 +1,6 @@
+import conf
+
+globals().update(vars(conf))
+ADDITIONAL_METADATA = {
+ "ID": "illegal"
+}
diff --git a/tests/data/test_config/prod.py b/tests/data/test_config/prod.py
new file mode 100644
index 0000000..3838827
--- /dev/null
+++ b/tests/data/test_config/prod.py
@@ -0,0 +1,6 @@
+import conf
+
+globals().update(vars(conf))
+ADDITIONAL_METADATA = {
+ "ID": "prod"
+}
diff --git a/tests/data/translated_titles/conf.py b/tests/data/translated_titles/conf.py
index 3d0d829..9e8a042 100644
--- a/tests/data/translated_titles/conf.py
+++ b/tests/data/translated_titles/conf.py
@@ -1,95 +1,16 @@
# -*- coding: utf-8 -*-
-
-from __future__ import unicode_literals
import time
-
-# !! This is the configuration of Nikola. !!#
-# !! You should edit it to your liking. !!#
-
-
-# ! Some settings can be different in different languages.
-# ! A comment stating (translatable) is used to denote those.
-# ! There are two ways to specify a translatable setting:
-# ! (a) BLOG_TITLE = "My Blog"
-# ! (b) BLOG_TITLE = {"en": "My Blog", "es": "Mi Blog"}
-# ! Option (a) is there for backwards compatibility and when you don't
-# ! want that setting translated.
-# ! Option (b) should be used for settings that are different in
-# ! different languages.
-
-
-# Data about this site
BLOG_AUTHOR = "Your Name" # (translatable)
BLOG_TITLE = "Demo Site" # (translatable)
-# This is the main URL for your site. It will be used
-# in a prominent link
SITE_URL = "https://example.com/"
-# This is the URL where nikola's output will be deployed.
-# If not set, defaults to SITE_URL
-# BASE_URL = "https://example.com/"
BLOG_EMAIL = "joe@demo.site"
BLOG_DESCRIPTION = "This is a demo site for Nikola." # (translatable)
-
-# Nikola is multilingual!
-#
-# Currently supported languages are:
-# bg Bulgarian
-# ca Catalan
-# cs Czech [ALTERNATIVELY cz]
-# de German
-# el Greek [NOT gr!]
-# en English
-# eo Esperanto
-# es Spanish
-# et Estonian
-# eu Basque
-# fa Persian
-# fi Finnish
-# fr French
-# hi Hindi
-# hr Croatian
-# it Italian
-# ja Japanese [NOT jp!]
-# nb Norwegian Bokmål
-# nl Dutch
-# pt_br Portuguese (Brasil)
-# pl Polish
-# ru Russian
-# sl Slovenian [NOT sl_si!]
-# tr Turkish (Turkey) [NOT tr_tr!]
-# ur Urdu
-# zh_cn Chinese (Simplified)
-#
-# If you want to use Nikola with a non-supported language you have to provide
-# a module containing the necessary translations
-# (cf. the modules at nikola/data/themes/base/messages/).
-# If a specific post is not translated to a language, then the version
-# in the default language will be shown instead.
-
-# What is the default language?
DEFAULT_LANG = "en"
-
-# What other languages do you have?
-# The format is {"translationcode" : "path/to/translation" }
-# the path will be used as a prefix for the generated pages location
TRANSLATIONS = {
"en": "",
"pl": "./pl",
}
-
-# What will translated input files be named like?
-
-# If you have a page something.rst, then something.pl.rst will be considered
-# its Polish translation.
-# (in the above example: path == "something", ext == "rst", lang == "pl")
-# this pattern is also used for metadata:
-# something.meta -> something.pl.meta
-
TRANSLATIONS_PATTERN = "{path}.{lang}.{ext}"
-
-# Links for the sidebar / navigation bar.
-# You should provide a key-value pair for each used language.
-# (the same way you would do with a (translatable) setting.)
NAVIGATION_LINKS = {
DEFAULT_LANG: (
('/archive.html', 'Archives'),
@@ -97,67 +18,14 @@ NAVIGATION_LINKS = {
('/rss.xml', 'RSS'),
),
}
-
-# Below this point, everything is optional
-
-# While nikola can select a sensible locale for each language,
-# sometimes explicit control can come handy.
-# In this file we express locales in the string form that
-# python's locales will accept in your OS, by example
-# "en_US.utf8" in unix-like OS, "English_United States" in Windows.
-# LOCALES = dict mapping language --> explicit locale for the languages
-# in TRANSLATIONS. You can ommit one or more keys.
-# LOCALE_FALLBACK = locale to use when an explicit locale is unavailable
-# LOCALE_DEFAULT = locale to use for languages not mentioned in LOCALES; if
-# not set the default Nikola mapping is used.
-
-# POSTS and PAGES contains (wildcard, destination, template) tuples.
-#
-# The wildcard is used to generate a list of reSt source files
-# (whatever/thing.txt).
-#
-# That fragment could have an associated metadata file (whatever/thing.meta),
-# and optionally translated files (example for spanish, with code "es"):
-# whatever/thing.es.txt and whatever/thing.es.meta
-#
-# This assumes you use the default TRANSLATIONS_PATTERN.
-#
-# From those files, a set of HTML fragment files will be generated:
-# cache/whatever/thing.html (and maybe cache/whatever/thing.html.es)
-#
-# These files are combinated with the template to produce rendered
-# pages, which will be placed at
-# output / TRANSLATIONS[lang] / destination / pagename.html
-#
-# where "pagename" is the "slug" specified in the metadata file.
-#
-# The difference between POSTS and PAGES is that POSTS are added
-# to feeds and are considered part of a blog, while PAGES are
-# just independent HTML pages.
-#
-
POSTS = (
("posts/*.rst", "posts", "post.tmpl"),
("posts/*.txt", "posts", "post.tmpl"),
)
PAGES = (
- ("stories/*.rst", "stories", "story.tmpl"),
- ("stories/*.txt", "stories", "story.tmpl"),
+ ("pages/*.rst", "pages", "page.tmpl"),
+ ("pages/*.txt", "pages", "page.tmpl"),
)
-
-# One or more folders containing files to be copied as-is into the output.
-# The format is a dictionary of "source" "relative destination".
-# Default is:
-# FILES_FOLDERS = {'files': '' }
-# Which means copy 'files' into 'output'
-
-# A mapping of languages to file-extensions that represent that language.
-# Feel free to add or delete extensions to any list, but don't add any new
-# compilers unless you write the interface for it yourself.
-#
-# 'rest' is reStructuredText
-# 'markdown' is MarkDown
-# 'html' assumes the file is html and just copies it
COMPILERS = {
"rest": ('.rst', '.txt'),
"markdown": ('.md', '.mdown', '.markdown'),
@@ -176,237 +44,10 @@ COMPILERS = {
# with many of the others.
# "pandoc": ('.rst', '.md', '.txt'),
}
-
-# Create by default posts in one file format?
-# Set to False for two-file posts, with separate metadata.
-# ONE_FILE_POSTS = True
-
-# If this is set to True, the DEFAULT_LANG version will be displayed for
-# untranslated posts.
-# If this is set to False, then posts that are not translated to a language
-# LANG will not be visible at all in the pages in that language.
-# Formerly known as HIDE_UNTRANSLATED_POSTS (inverse)
-# SHOW_UNTRANSLATED_POSTS = True
-
-# Paths for different autogenerated bits. These are combined with the
-# translation paths.
-
-# Final locations are:
-# output / TRANSLATION[lang] / TAG_PATH / index.html (list of tags)
-# output / TRANSLATION[lang] / TAG_PATH / tag.html (list of posts for a tag)
-# output / TRANSLATION[lang] / TAG_PATH / tag.xml (RSS feed for a tag)
-# TAG_PATH = "categories"
-
-# If TAG_PAGES_ARE_INDEXES is set to True, each tag's page will contain
-# the posts themselves. If set to False, it will be just a list of links.
-# TAG_PAGES_ARE_INDEXES = True
-
-# Final location for the main blog page and sibling paginated pages is
-# output / TRANSLATION[lang] / INDEX_PATH / index-*.html
-# INDEX_PATH = ""
-
-# Create per-month archives instead of per-year
-# CREATE_MONTHLY_ARCHIVE = False
-# Create one large archive instead of per-year
-# CREATE_SINGLE_ARCHIVE = False
-# Final locations for the archives are:
-# output / TRANSLATION[lang] / ARCHIVE_PATH / ARCHIVE_FILENAME
-# output / TRANSLATION[lang] / ARCHIVE_PATH / YEAR / index.html
-# output / TRANSLATION[lang] / ARCHIVE_PATH / YEAR / MONTH / index.html
-# ARCHIVE_PATH = ""
-# ARCHIVE_FILENAME = "archive.html"
-
-# URLs to other posts/pages can take 3 forms:
-# rel_path: a relative URL to the current page/post (default)
-# full_path: a URL with the full path from the root
-# absolute: a complete URL (that includes the SITE_URL)
-# URL_TYPE = 'rel_path'
-
-# Final location for the blog main RSS feed is:
-# output / TRANSLATION[lang] / RSS_PATH / rss.xml
-# RSS_PATH = ""
-
-# Number of posts in RSS feeds
-# FEED_LENGTH = 10
-
-# Slug the Tag URL easier for users to type, special characters are
-# often removed or replaced as well.
-# SLUG_TAG_PATH = True
-
-# A list of redirection tuples, [("foo/from.html", "/bar/to.html")].
-#
-# A HTML file will be created in output/foo/from.html that redirects
-# to the "/bar/to.html" URL. notice that the "from" side MUST be a
-# relative URL.
-#
-# If you don't need any of these, just set to []
REDIRECTIONS = []
-
-# Commands to execute to deploy. Can be anything, for example,
-# you may use rsync:
-# "rsync -rav --delete output/ joe@my.site:/srv/www/site"
-# And then do a backup, or run `nikola ping` from the `ping`
-# plugin (`nikola install_plugin ping`).
-# To do manual deployment, set it to []
-# DEPLOY_COMMANDS = []
-
-# Where the output site should be located
-# If you don't use an absolute path, it will be considered as relative
-# to the location of conf.py
-# OUTPUT_FOLDER = 'output'
-
-# where the "cache" of partial generated content should be located
-# default: 'cache'
-# CACHE_FOLDER = 'cache'
-
-# Filters to apply to the output.
-# A directory where the keys are either: a file extensions, or
-# a tuple of file extensions.
-#
-# And the value is a list of commands to be applied in order.
-#
-# Each command must be either:
-#
-# A string containing a '%s' which will
-# be replaced with a filename. The command *must* produce output
-# in place.
-#
-# Or:
-#
-# A python callable, which will be called with the filename as
-# argument.
-#
-# By default, there are no filters.
-#
-# Many filters are shipped with Nikola. A list is available in the manual:
-# <https://getnikola.com/handbook.html#post-processing-filters>
-# FILTERS = {
-# ".jpg": ["jpegoptim --strip-all -m75 -v %s"],
-# }
-
-# Expert setting! Create a gzipped copy of each generated file. Cheap server-
-# side optimization for very high traffic sites or low memory servers.
-# GZIP_FILES = False
-# File extensions that will be compressed
-# GZIP_EXTENSIONS = ('.txt', '.htm', '.html', '.css', '.js', '.json', '.xml')
-# Use an external gzip command? None means no.
-# Example: GZIP_COMMAND = "pigz -k {filename}"
-# GZIP_COMMAND = None
-# Make sure the server does not return a "Accept-Ranges: bytes" header for
-# files compressed by this option! OR make sure that a ranged request does not
-# return partial content of another representation for these resources. Do not
-# use this feature if you do not understand what this means.
-
-# Compiler to process LESS files.
-# LESS_COMPILER = 'lessc'
-
-# A list of options to pass to the LESS compiler.
-# Final command is: LESS_COMPILER LESS_OPTIONS file.less
-# LESS_OPTIONS = []
-
-# Compiler to process Sass files.
-# SASS_COMPILER = 'sass'
-
-# A list of options to pass to the Sass compiler.
-# Final command is: SASS_COMPILER SASS_OPTIONS file.s(a|c)ss
-# SASS_OPTIONS = []
-
-# #############################################################################
-# Image Gallery Options
-# #############################################################################
-
-# Galleries are folders in galleries/
-# Final location of galleries will be output / GALLERY_PATH / gallery_name
-# GALLERY_PATH = "galleries"
-# THUMBNAIL_SIZE = 180
-# MAX_IMAGE_SIZE = 1280
-# USE_FILENAME_AS_TITLE = True
-# EXTRA_IMAGE_EXTENSIONS = []
-#
-# If set to False, it will sort by filename instead. Defaults to True
-# GALLERY_SORT_BY_DATE = True
-
-# #############################################################################
-# HTML fragments and diverse things that are used by the templates
-# #############################################################################
-
-# Data about post-per-page indexes.
-# INDEXES_PAGES defaults to 'old posts, page %d' or 'page %d' (translated),
-# depending on the value of INDEXES_PAGES_MAIN.
-# INDEXES_TITLE = "" # If this is empty, defaults to BLOG_TITLE
-# INDEXES_PAGES = "" # If this is empty, defaults to '[old posts,] page %d' (see above)
-# INDEXES_PAGES_MAIN = False # If True, INDEXES_PAGES is also displayed on
-# # the main (the newest) index page (index.html)
-
-# Name of the theme to use.
-THEME = "bootstrap3"
-
-# Color scheme to be used for code blocks. If your theme provides
-# "assets/css/code.css" this is ignored.
-# Can be any of autumn borland bw colorful default emacs friendly fruity manni
-# monokai murphy native pastie perldoc rrt tango trac vim vs
-# CODE_COLOR_SCHEME = 'default'
-
-# If you use 'site-reveal' theme you can select several subthemes
-# THEME_REVEAL_CONFIG_SUBTHEME = 'sky'
-# You can also use: beige/serif/simple/night/default
-
-# Again, if you use 'site-reveal' theme you can select several transitions
-# between the slides
-# THEME_REVEAL_CONFIG_TRANSITION = 'cube'
-# You can also use: page/concave/linear/none/default
-
-# date format used to display post dates.
-# (str used by datetime.datetime.strftime)
-# DATE_FORMAT = '%Y-%m-%d %H:%M'
-
-# FAVICONS contains (name, file, size) tuples.
-# Used for create favicon link like this:
-# <link rel="name" href="file" sizes="size"/>
-# For creating favicons, take a look at:
-# http://www.netmagazine.com/features/create-perfect-favicon
-# FAVICONS = {
-# ("icon", "/favicon.ico", "16x16"),
-# ("icon", "/icon_128x128.png", "128x128"),
-# }
-
-# Show only teasers in the index pages? Defaults to False.
-# INDEX_TEASERS = False
-
-# A HTML fragment with the Read more... link.
-# The following tags exist and are replaced for you:
-# {link} A link to the full post page.
-# {read_more} The string “Read more” in the current language.
-# {{ A literal { (U+007B LEFT CURLY BRACKET)
-# }} A literal } (U+007D RIGHT CURLY BRACKET)
-# READ_MORE_LINK = '<p class="more"><a href="{link}">{read_more}…</a></p>'
-
-# A HTML fragment describing the license, for the sidebar.
-# (translatable)
+THEME = "bootblog4"
LICENSE = ""
-# I recommend using the Creative Commons' wizard:
-# http://creativecommons.org/choose/
-# LICENSE = """
-# <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/2.5/ar/">
-# <img alt="Creative Commons License BY-NC-SA"
-# style="border-width:0; margin-bottom:12px;"
-# src="http://i.creativecommons.org/l/by-nc-sa/2.5/ar/88x31.png"></a>"""
-
-# A small copyright notice for the page footer (in HTML).
-# (translatable)
CONTENT_FOOTER = 'Contents &copy; {date} <a href="mailto:{email}">{author}</a> - Powered by <a href="https://getnikola.com/" rel="nofollow">Nikola</a> {license}'
-
-# Things that will be passed to CONTENT_FOOTER.format(). This is done
-# for translatability, as dicts are not formattable. Nikola will
-# intelligently format the setting properly.
-# The setting takes a dict. The keys are languages. The values are
-# tuples of tuples of positional arguments and dicts of keyword arguments
-# to format(). For example, {'en': (('Hello'), {'target': 'World'})}
-# results in CONTENT_FOOTER['en'].format('Hello', target='World').
-# WARNING: If you do not use multiple languages with CONTENT_FOOTER, this
-# still needs to be a dict of this format. (it can be empty if you
-# do not need formatting)
-# (translatable)
CONTENT_FOOTER_FORMATS = {
DEFAULT_LANG: (
(),
@@ -418,335 +59,6 @@ CONTENT_FOOTER_FORMATS = {
}
)
}
-
-# To use comments, you can choose between different third party comment
-# systems, one of "disqus", "livefyre", "intensedebate", "moot",
-# "googleplus", "facebook" or "isso"
COMMENT_SYSTEM = "disqus"
-# And you also need to add your COMMENT_SYSTEM_ID which
-# depends on what comment system you use. The default is
-# "nikolademo" which is a test account for Disqus. More information
-# is in the manual.
COMMENT_SYSTEM_ID = "nikolademo"
-
-# Enable annotations using annotateit.org?
-# If set to False, you can still enable them for individual posts and pages
-# setting the "annotations" metadata.
-# If set to True, you can disable them for individual posts and pages using
-# the "noannotations" metadata.
-# ANNOTATIONS = False
-
-# Create index.html for story folders?
-# STORY_INDEX = False
-# Enable comments on story pages?
-# COMMENTS_IN_STORIES = False
-# Enable comments on picture gallery pages?
-# COMMENTS_IN_GALLERIES = False
-
-# What file should be used for directory indexes?
-# Defaults to index.html
-# Common other alternatives: default.html for IIS, index.php
-# INDEX_FILE = "index.html"
-
-# If a link ends in /index.html, drop the index.html part.
-# http://mysite/foo/bar/index.html => http://mysite/foo/bar/
-# (Uses the INDEX_FILE setting, so if that is, say, default.html,
-# it will instead /foo/default.html => /foo)
-# (Note: This was briefly STRIP_INDEX_HTML in v 5.4.3 and 5.4.4)
-# Default = False
-# STRIP_INDEXES = False
-
-# Should the sitemap list directories which only include other directories
-# and no files.
-# Default to True
-# If this is False
-# e.g. /2012 includes only /01, /02, /03, /04, ...: don't add it to the sitemap
-# if /2012 includes any files (including index.html)... add it to the sitemap
-# SITEMAP_INCLUDE_FILELESS_DIRS = True
-
-# Instead of putting files in <slug>.html, put them in
-# <slug>/index.html. Also enables STRIP_INDEXES
-# This can be disabled on a per-page/post basis by adding
-# .. pretty_url: False
-# to the metadata
-# PRETTY_URLS = False
-
-# If True, publish future dated posts right away instead of scheduling them.
-# Defaults to False.
-# FUTURE_IS_NOW = False
-
-# If True, future dated posts are allowed in deployed output
-# Only the individual posts are published/deployed; not in indexes/sitemap
-# Generally, you want FUTURE_IS_NOW and DEPLOY_FUTURE to be the same value.
-# DEPLOY_FUTURE = False
-# If False, draft posts will not be deployed
-# DEPLOY_DRAFTS = True
-
-# Allows scheduling of posts using the rule specified here (new_post -s)
-# Specify an iCal Recurrence Rule: http://www.kanzaki.com/docs/ical/rrule.html
-# SCHEDULE_RULE = ''
-# If True, use the scheduling rule to all posts by default
-# SCHEDULE_ALL = False
-# If True, schedules post to today if possible, even if scheduled hour is over
-# SCHEDULE_FORCE_TODAY = False
-
-# Do you want a add a Mathjax config file?
-# MATHJAX_CONFIG = ""
-
-# If you are using the compile-ipynb plugin, just add this one:
-# MATHJAX_CONFIG = """
-# <script type="text/x-mathjax-config">
-# MathJax.Hub.Config({
-# tex2jax: {
-# inlineMath: [ ['$','$'], ["\\\(","\\\)"] ],
-# displayMath: [ ['$$','$$'], ["\\\[","\\\]"] ]
-# },
-# displayAlign: 'left', // Change this to 'center' to center equations.
-# "HTML-CSS": {
-# styles: {'.MathJax_Display': {"margin": 0}}
-# }
-# });
-# </script>
-# """
-
-# Do you want to customize the nbconversion of your IPython notebook?
-# IPYNB_CONFIG = {}
-# With the following example configuracion you can use a custom jinja template
-# called `toggle.tpl` which has to be located in your site/blog main folder:
-# IPYNB_CONFIG = {'Exporter':{'template_file': 'toggle'}}
-
-# What MarkDown extensions to enable?
-# You will also get gist, nikola and podcast because those are
-# done in the code, hope you don't mind ;-)
-# MARKDOWN_EXTENSIONS = ['fenced_code', 'codehilite']
-
-# Social buttons. This is sample code for AddThis (which was the default for a
-# long time). Insert anything you want here, or even make it empty.
-# (translatable)
-# SOCIAL_BUTTONS_CODE = """
-# <!-- Social buttons -->
-# <div id="addthisbox" class="addthis_toolbox addthis_peekaboo_style addthis_default_style addthis_label_style addthis_32x32_style">
-# <a class="addthis_button_more">Share</a>
-# <ul><li><a class="addthis_button_facebook"></a>
-# <li><a class="addthis_button_google_plusone_share"></a>
-# <li><a class="addthis_button_linkedin"></a>
-# <li><a class="addthis_button_twitter"></a>
-# </ul>
-# </div>
-# <script src="//s7.addthis.com/js/300/addthis_widget.js#pubid=ra-4f7088a56bb93798"></script>
-# <!-- End of social buttons -->
-# """
-
-# Show link to source for the posts?
-# Formerly known as HIDE_SOURCELINK (inverse)
-# SHOW_SOURCELINK = True
-# Copy the source files for your pages?
-# Setting it to False implies SHOW_SOURCELINK = False
-# COPY_SOURCES = True
-
-# Modify the number of Post per Index Page
-# Defaults to 10
-# INDEX_DISPLAY_POST_COUNT = 10
-
-# RSS_LINK is a HTML fragment to link the RSS or Atom feeds. If set to None,
-# the base.tmpl will use the feed Nikola generates. However, you may want to
-# change it for a feedburner feed or something else.
-# RSS_LINK = None
-
-# Show only teasers in the RSS feed? Default to True
-# RSS_TEASERS = True
-
-# Strip HTML in the RSS feed? Default to False
-# RSS_PLAIN = False
-
-# A search form to search this site, for the sidebar. You can use a google
-# custom search (http://www.google.com/cse/)
-# Or a duckduckgo search: https://duckduckgo.com/search_box.html
-# Default is no search form.
-# (translatable)
-# SEARCH_FORM = ""
-#
-# This search form works for any site and looks good in the "site" theme where
-# it appears on the navigation bar:
-#
-# SEARCH_FORM = """
-# <!-- Custom search -->
-# <form method="get" id="search" action="//duckduckgo.com/"
-# class="navbar-form pull-left">
-# <input type="hidden" name="sites" value="%s"/>
-# <input type="hidden" name="k8" value="#444444"/>
-# <input type="hidden" name="k9" value="#D51920"/>
-# <input type="hidden" name="kt" value="h"/>
-# <input type="text" name="q" maxlength="255"
-# placeholder="Search&hellip;" class="span2" style="margin-top: 4px;"/>
-# <input type="submit" value="DuckDuckGo Search" style="visibility: hidden;" />
-# </form>
-# <!-- End of custom search -->
-# """ % SITE_URL
-#
-# If you prefer a google search form, here's an example that should just work:
-# SEARCH_FORM = """
-# <!-- Custom search with google-->
-# <form id="search" action="//www.google.com/search" method="get" class="navbar-form pull-left">
-# <input type="hidden" name="q" value="site:%s" />
-# <input type="text" name="q" maxlength="255" results="0" placeholder="Search"/>
-# </form>
-# <!-- End of custom search -->
-# """ % SITE_URL
-
-# Also, there is a local search plugin you can use, based on Tipue, but it requires setting several
-# options:
-
-# SEARCH_FORM = """
-# <span class="navbar-form pull-left">
-# <input type="text" id="tipue_search_input">
-# </span>"""
-#
-# BODY_END = """
-# <script src="/assets/js/tipuesearch_set.js"></script>
-# <script src="/assets/js/tipuesearch.js"></script>
-# <script>
-# $(document).ready(function() {
-# $('#tipue_search_input').tipuesearch({
-# 'mode': 'json',
-# 'contentLocation': '/assets/js/tipuesearch_content.json',
-# 'showUrl': false
-# });
-# });
-# </script>
-# """
-
-# EXTRA_HEAD_DATA = """
-# <link rel="stylesheet" type="text/css" href="/assets/css/tipuesearch.css">
-# <div id="tipue_search_content" style="margin-left: auto; margin-right: auto; padding: 20px;"></div>
-# """
-# ENABLED_EXTRAS = ['local_search']
-#
-
-
-# Use content distribution networks for jquery and twitter-bootstrap css and js
-# If this is True, jquery and html5shiv are served from the Google CDN and
-# Bootstrap is served from BootstrapCDN (provided by MaxCDN)
-# Set this to False if you want to host your site without requiring access to
-# external resources.
-# USE_CDN = False
-
-# Extra things you want in the pages HEAD tag. This will be added right
-# before </head>
-# (translatable)
-# EXTRA_HEAD_DATA = ""
-# Google Analytics or whatever else you use. Added to the bottom of <body>
-# in the default template (base.tmpl).
-# (translatable)
-# BODY_END = ""
-
-# The possibility to extract metadata from the filename by using a
-# regular expression.
-# To make it work you need to name parts of your regular expression.
-# The following names will be used to extract metadata:
-# - title
-# - slug
-# - date
-# - tags
-# - link
-# - description
-#
-# An example re is the following:
-# '(?P<date>\d{4}-\d{2}-\d{2})-(?P<slug>.*)-(?P<title>.*)\.md'
-# FILE_METADATA_REGEXP = None
-
-# Additional metadata that is added to a post when creating a new_post
-# ADDITIONAL_METADATA = {}
-
-# Nikola supports Twitter Card summaries / Open Graph.
-# Twitter cards make it possible for you to attach media to Tweets
-# that link to your content.
-#
-# IMPORTANT:
-# Please note, that you need to opt-in for using Twitter Cards!
-# To do this please visit
-# https://dev.twitter.com/form/participate-twitter-cards
-#
-# Uncomment and modify to following lines to match your accounts.
-# Specifying the id for either 'site' or 'creator' will be preferred
-# over the cleartext username. Specifying an ID is not necessary.
-# Displaying images is currently not supported.
-# TWITTER_CARD = {
-# # 'use_twitter_cards': True, # enable Twitter Cards / Open Graph
-# # 'site': '@website', # twitter nick for the website
-# # 'site:id': 123456, # Same as site, but the website's Twitter user ID
-# # instead.
-# # 'creator': '@username', # Username for the content creator / author.
-# # 'creator:id': 654321, # Same as creator, but the Twitter user's ID.
-# }
-
-
-# Post's dates are considered in UTC by default, if you want to use
-# another time zone, please set TIMEZONE to match. Check the available
-# list from Wikipedia:
-# http://en.wikipedia.org/wiki/List_of_tz_database_time_zones
-# (eg. 'Europe/Zurich')
-# Also, if you want to use a different time zone in some of your posts,
-# you can use W3C-DTF Format (ex. 2012-03-30T23:00:00+02:00)
-#
-# TIMEZONE = 'UTC'
-
-# If webassets is installed, bundle JS and CSS to make site loading faster
-# USE_BUNDLES = True
-
-# Plugins you don't want to use. Be careful :-)
-# DISABLED_PLUGINS = ["render_galleries"]
-
-# Add the absolute paths to directories containing plugins to use them.
-# For example, the `plugins` directory of your clone of the Nikola plugins
-# repository.
-# EXTRA_PLUGINS_DIRS = []
-
-# Experimental plugins - use at your own risk.
-# They probably need some manual adjustments - please see their respective
-# readme.
-# ENABLED_EXTRAS = [
-# 'planetoid',
-# 'ipynb',
-# 'local_search',
-# 'render_mustache',
-# ]
-
-# List of regular expressions, links matching them will always be considered
-# valid by "nikola check -l"
-# LINK_CHECK_WHITELIST = []
-
-# If set to True, enable optional hyphenation in your posts (requires pyphen)
-# HYPHENATE = False
-
-# The <hN> tags in HTML generated by certain compilers (reST/Markdown)
-# will be demoted by that much (1 → h1 will become h2 and so on)
-# This was a hidden feature of the Markdown and reST compilers in the
-# past. Useful especially if your post titles are in <h1> tags too, for
-# example.
-# (defaults to 1.)
-# DEMOTE_HEADERS = 1
-
-# You can configure the logging handlers installed as plugins or change the
-# log level of the default stdout handler.
-LOGGING_HANDLERS = {
- 'stderr': {'loglevel': 'WARNING', 'bubble': True},
- # 'smtp': {
- # 'from_addr': 'test-errors@example.com',
- # 'recipients': ('test@example.com'),
- # 'credentials':('testusername', 'password'),
- # 'server_addr': ('127.0.0.1', 25),
- # 'secure': (),
- # 'level': 'DEBUG',
- # 'bubble': True
- # }
-}
-
-# Templates will use those filters, along with the defaults.
-# Consult your engine's documentation on filters if you need help defining
-# those.
-# TEMPLATE_FILTERS = {}
-
-# Put in global_context things you want available on all your templates.
-# It can be anything, data, functions, modules, etc.
GLOBAL_CONTEXT = {}
diff --git a/tests/data/translated_titles/stories/1.pl.txt b/tests/data/translated_titles/pages/1.pl.txt
index a888c1f..a888c1f 100644
--- a/tests/data/translated_titles/stories/1.pl.txt
+++ b/tests/data/translated_titles/pages/1.pl.txt
diff --git a/tests/data/translated_titles/stories/1.txt b/tests/data/translated_titles/pages/1.txt
index 45fb214..45fb214 100644
--- a/tests/data/translated_titles/stories/1.txt
+++ b/tests/data/translated_titles/pages/1.txt
diff --git a/tests/wordpress_export_example.xml b/tests/data/wordpress_import/wordpress_export_example.xml
index e2401f7..e2401f7 100644
--- a/tests/wordpress_export_example.xml
+++ b/tests/data/wordpress_import/wordpress_export_example.xml
diff --git a/tests/data/wordpress_import/wordpress_qtranslate_item_modernized.xml b/tests/data/wordpress_import/wordpress_qtranslate_item_modernized.xml
new file mode 100644
index 0000000..2622bfd
--- /dev/null
+++ b/tests/data/wordpress_import/wordpress_qtranslate_item_modernized.xml
@@ -0,0 +1,30 @@
+ <item>
+ <title>[:fr]Sous le ciel[:][:en]Under heaven[:]</title>
+ <link>http://www.tibonihoo.net/blog/2014/05/sous-le-ciel/</link>
+ <pubDate>Sat, 03 May 2014 13:20:32 +0000</pubDate>
+ <dc:creator><![CDATA[tibonihoo_admin]]></dc:creator>
+ <guid isPermaLink="false">http://www.tibonihoo.net/blog/?p=1585</guid>
+ <description></description>
+ <content:encoded><![CDATA[[:fr]<a href="http://www.tibonihoo.net/blog/wp-content/uploads/2014/05/IMG_68511.jpg"><img class="alignnone size-medium wp-image-1587" src="http://www.tibonihoo.net/blog/wp-content/uploads/2014/05/IMG_68511-300x199.jpg" alt="IMG_6851" width="300" height="199" /></a>[:][:en]<img class="alignnone size-medium wp-image-1587" src="http://www.tibonihoo.net/blog/wp-content/uploads/2014/05/IMG_68511-300x199.jpg" alt="IMG_6851" width="300" height="199" />[:]]]></content:encoded>
+ <excerpt:encoded><![CDATA[]]></excerpt:encoded>
+ <wp:post_id>1585</wp:post_id>
+ <wp:post_date><![CDATA[2014-05-03 14:20:32]]></wp:post_date>
+ <wp:post_date_gmt><![CDATA[2014-05-03 13:20:32]]></wp:post_date_gmt>
+ <wp:comment_status><![CDATA[open]]></wp:comment_status>
+ <wp:ping_status><![CDATA[closed]]></wp:ping_status>
+ <wp:post_name><![CDATA[sous-le-ciel]]></wp:post_name>
+ <wp:status><![CDATA[publish]]></wp:status>
+ <wp:post_parent>0</wp:post_parent>
+ <wp:menu_order>0</wp:menu_order>
+ <wp:post_type><![CDATA[post]]></wp:post_type>
+ <wp:post_password><![CDATA[]]></wp:post_password>
+ <wp:is_sticky>0</wp:is_sticky>
+ <category domain="post_tag" nicename="chine"><![CDATA[Chine]]></category>
+ <category domain="category" nicename="creations"><![CDATA[créations]]></category>
+ <category domain="post_tag" nicename="photos"><![CDATA[photos]]></category>
+ <category domain="post_tag" nicename="roof"><![CDATA[roof]]></category>
+ <wp:postmeta>
+ <wp:meta_key><![CDATA[_edit_last]]></wp:meta_key>
+ <wp:meta_value><![CDATA[2]]></wp:meta_value>
+ </wp:postmeta>
+ </item>
diff --git a/tests/data/wordpress_import/wordpress_qtranslate_item_raw_export.xml b/tests/data/wordpress_import/wordpress_qtranslate_item_raw_export.xml
new file mode 100644
index 0000000..50bac7b
--- /dev/null
+++ b/tests/data/wordpress_import/wordpress_qtranslate_item_raw_export.xml
@@ -0,0 +1,30 @@
+ <item>
+ <title><!--:fr-->Sous le ciel<!--:--><!--:en-->Under heaven<!--:--></title>
+ <link>http://www.tibonihoo.net/blog/2014/05/sous-le-ciel/</link>
+ <pubDate>Sat, 03 May 2014 13:20:32 +0000</pubDate>
+ <dc:creator><![CDATA[tibonihoo_admin]]></dc:creator>
+ <guid isPermaLink="false">http://www.tibonihoo.net/blog/?p=1585</guid>
+ <description></description>
+ <content:encoded><![CDATA[<!--:fr--><a href="http://www.tibonihoo.net/blog/wp-content/uploads/2014/05/IMG_68511.jpg"><img class="alignnone size-medium wp-image-1587" src="http://www.tibonihoo.net/blog/wp-content/uploads/2014/05/IMG_68511-300x199.jpg" alt="IMG_6851" width="300" height="199" /></a><!--:--><!--:en--><img class="alignnone size-medium wp-image-1587" src="http://www.tibonihoo.net/blog/wp-content/uploads/2014/05/IMG_68511-300x199.jpg" alt="IMG_6851" width="300" height="199" /><!--:-->]]></content:encoded>
+ <excerpt:encoded><![CDATA[]]></excerpt:encoded>
+ <wp:post_id>1585</wp:post_id>
+ <wp:post_date><![CDATA[2014-05-03 14:20:32]]></wp:post_date>
+ <wp:post_date_gmt><![CDATA[2014-05-03 13:20:32]]></wp:post_date_gmt>
+ <wp:comment_status><![CDATA[open]]></wp:comment_status>
+ <wp:ping_status><![CDATA[closed]]></wp:ping_status>
+ <wp:post_name><![CDATA[sous-le-ciel]]></wp:post_name>
+ <wp:status><![CDATA[publish]]></wp:status>
+ <wp:post_parent>0</wp:post_parent>
+ <wp:menu_order>0</wp:menu_order>
+ <wp:post_type><![CDATA[post]]></wp:post_type>
+ <wp:post_password><![CDATA[]]></wp:post_password>
+ <wp:is_sticky>0</wp:is_sticky>
+ <category domain="post_tag" nicename="chine"><![CDATA[Chine]]></category>
+ <category domain="category" nicename="creations"><![CDATA[créations]]></category>
+ <category domain="post_tag" nicename="photos"><![CDATA[photos]]></category>
+ <category domain="post_tag" nicename="roof"><![CDATA[roof]]></category>
+ <wp:postmeta>
+ <wp:meta_key><![CDATA[_edit_last]]></wp:meta_key>
+ <wp:meta_value><![CDATA[2]]></wp:meta_value>
+ </wp:postmeta>
+ </item>
diff --git a/tests/wordpress_unicode_export.xml b/tests/data/wordpress_import/wordpress_unicode_export.xml
index b2204fc..b2204fc 100644
--- a/tests/wordpress_unicode_export.xml
+++ b/tests/data/wordpress_import/wordpress_unicode_export.xml
diff --git a/tests/helper.py b/tests/helper.py
new file mode 100644
index 0000000..36558da
--- /dev/null
+++ b/tests/helper.py
@@ -0,0 +1,131 @@
+"""
+Helper utilities and classes for Nikola tests.
+
+Alongside a contextmanager to switch directories this module contains
+a Site substitute for rendering tests.
+"""
+
+import os
+from contextlib import contextmanager
+
+from yapsy.PluginManager import PluginManager
+
+import nikola.utils
+import nikola.shortcodes
+from nikola.plugin_categories import (
+ Command,
+ Task,
+ LateTask,
+ TemplateSystem,
+ PageCompiler,
+ TaskMultiplier,
+ CompilerExtension,
+ MarkdownExtension,
+ RestExtension,
+)
+
+__all__ = ["cd", "FakeSite"]
+
+
+@contextmanager
+def cd(path):
+ old_dir = os.getcwd()
+ os.chdir(path)
+ yield
+ os.chdir(old_dir)
+
+
+class FakeSite:
+ def __init__(self):
+ self.template_system = self
+ self.invariant = False
+ self.debug = True
+ self.config = {
+ "DISABLED_PLUGINS": [],
+ "EXTRA_PLUGINS": [],
+ "DEFAULT_LANG": "en",
+ "MARKDOWN_EXTENSIONS": [
+ "markdown.extensions.fenced_code",
+ "markdown.extensions.codehilite",
+ ],
+ "TRANSLATIONS_PATTERN": "{path}.{lang}.{ext}",
+ "LISTINGS_FOLDERS": {"listings": "listings"},
+ "TRANSLATIONS": {"en": ""},
+ }
+ self.EXTRA_PLUGINS = self.config["EXTRA_PLUGINS"]
+ self.plugin_manager = PluginManager(
+ categories_filter={
+ "Command": Command,
+ "Task": Task,
+ "LateTask": LateTask,
+ "TemplateSystem": TemplateSystem,
+ "PageCompiler": PageCompiler,
+ "TaskMultiplier": TaskMultiplier,
+ "CompilerExtension": CompilerExtension,
+ "MarkdownExtension": MarkdownExtension,
+ "RestExtension": RestExtension,
+ }
+ )
+ self.shortcode_registry = {}
+ self.plugin_manager.setPluginInfoExtension("plugin")
+ places = [os.path.join(os.path.dirname(nikola.utils.__file__), "plugins")]
+ self.plugin_manager.setPluginPlaces(places)
+ self.plugin_manager.collectPlugins()
+ self.compiler_extensions = self._activate_plugins_of_category(
+ "CompilerExtension"
+ )
+
+ self.timeline = [FakePost(title="Fake post", slug="fake-post")]
+ self.rst_transforms = []
+ self.post_per_input_file = {}
+ # This is to make plugin initialization happy
+ self.template_system = self
+ self.name = "mako"
+
+ def _activate_plugins_of_category(self, category):
+ """Activate all the plugins of a given category and return them."""
+ # this code duplicated in nikola/nikola.py
+ plugins = []
+ for plugin_info in self.plugin_manager.getPluginsOfCategory(category):
+ if plugin_info.name in self.config.get("DISABLED_PLUGINS"):
+ self.plugin_manager.removePluginFromCategory(plugin_info, category)
+ else:
+ self.plugin_manager.activatePluginByName(plugin_info.name)
+ plugin_info.plugin_object.set_site(self)
+ plugins.append(plugin_info)
+ return plugins
+
+ def render_template(self, name, _, context):
+ return '<img src="IMG.jpg">'
+
+ # this code duplicated in nikola/nikola.py
+ def register_shortcode(self, name, f):
+ """Register function f to handle shortcode "name"."""
+ if name in self.shortcode_registry:
+ nikola.utils.LOGGER.warning('Shortcode name conflict: %s', name)
+ return
+ self.shortcode_registry[name] = f
+
+ def apply_shortcodes(self, data, *a, **kw):
+ """Apply shortcodes from the registry on data."""
+ return nikola.shortcodes.apply_shortcodes(data, self.shortcode_registry, **kw)
+
+ def apply_shortcodes_uuid(self, data, shortcodes, *a, **kw):
+ """Apply shortcodes from the registry on data."""
+ return nikola.shortcodes.apply_shortcodes(data, self.shortcode_registry, **kw)
+
+
+class FakePost:
+ def __init__(self, title, slug):
+ self._title = title
+ self._slug = slug
+ self._meta = {"slug": slug}
+
+ def title(self):
+ return self._title
+
+ def meta(self, key):
+ return self._meta[key]
+
+ def permalink(self):
+ return "/posts/" + self._slug
diff --git a/tests/import_wordpress_and_build_workflow.py b/tests/import_wordpress_and_build_workflow.py
deleted file mode 100644
index bc04a1f..0000000
--- a/tests/import_wordpress_and_build_workflow.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-Script to test the import workflow.
-
-It will remove an existing Nikola installation and then install from the
-package directory.
-After that it will do create a new site with the import_wordpress
-command and use that newly created site to make a build.
-"""
-from __future__ import unicode_literals, print_function
-
-import os
-import shutil
-
-TEST_SITE_DIRECTORY = 'import_test_site'
-
-
-def main(import_directory=None):
- if import_directory is None:
- import_directory = TEST_SITE_DIRECTORY
-
- if os.path.exists(import_directory):
- print('deleting %s' % import_directory)
- shutil.rmtree(import_directory)
-
- test_directory = os.path.dirname(__file__)
- package_directory = os.path.abspath(os.path.join(test_directory, '..'))
-
- os.system('pip uninstall -y Nikola')
- os.system('pip install %s' % package_directory)
- os.system('nikola')
- import_file = os.path.join(test_directory, 'wordpress_export_example.xml')
- os.system(
- 'nikola import_wordpress -o {folder} {file}'.format(file=import_file,
- folder=import_directory))
-
- assert os.path.exists(
- import_directory), "The directory %s should be existing."
- os.chdir(import_directory)
- os.system('nikola build')
-
-if __name__ == '__main__':
- main()
diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py
new file mode 100644
index 0000000..ee2f4ea
--- /dev/null
+++ b/tests/integration/__init__.py
@@ -0,0 +1,12 @@
+"""
+Various integration tests for the Nikola commands.
+
+Each test module provides a different build fixture that will create a
+build in the desired way that then later can be checked in test
+functions.
+To avoid duplicating code many of the fixtures are shared between
+modules.
+
+The build fixtures are scoped for module level in order to avoid
+re-building the whole site for every test and to make these tests fast.
+"""
diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py
new file mode 100644
index 0000000..a87cb07
--- /dev/null
+++ b/tests/integration/conftest.py
@@ -0,0 +1,74 @@
+import os
+import sys
+
+import docutils
+import pytest
+
+from nikola.utils import LocaleBorg
+from ..helper import FakeSite
+
+
+@pytest.fixture(scope="module")
+def test_dir():
+ """
+ Absolute path to the directory with the tests.
+ """
+ return os.path.abspath(os.path.dirname(__file__))
+
+
+@pytest.fixture(scope="session")
+def other_locale() -> str:
+ return os.environ.get("NIKOLA_LOCALE_OTHER", "pl")
+
+
+@pytest.fixture(scope="module")
+def output_dir(target_dir):
+ return os.path.join(target_dir, "output")
+
+
+@pytest.fixture(scope="module")
+def target_dir(tmpdir_factory):
+ tdir = tmpdir_factory.mktemp("integration").join("target")
+ yield str(tdir)
+
+
+@pytest.fixture(scope="module", autouse=True)
+def remove_conf_module():
+ """
+ Remove the module `conf` from `sys.modules` after loading the config.
+
+ Fixes issue #438
+ """
+ try:
+ yield
+ finally:
+ try:
+ del sys.modules["conf"]
+ except KeyError:
+ pass
+
+
+@pytest.fixture(scope="module", autouse=True)
+def localeborg_setup(default_locale):
+ """
+ Reset the LocaleBorg before and after every test.
+ """
+ LocaleBorg.reset()
+ LocaleBorg.initialize({}, default_locale)
+ try:
+ yield
+ finally:
+ LocaleBorg.reset()
+
+
+@pytest.fixture(autouse=True, scope="module")
+def fix_leaked_state():
+ """Fix leaked state from integration tests"""
+ try:
+ yield
+ finally:
+ try:
+ func = docutils.parsers.rst.roles.role("doc", None, None, None)[0]
+ func.site = FakeSite()
+ except AttributeError:
+ pass
diff --git a/tests/integration/helper.py b/tests/integration/helper.py
new file mode 100644
index 0000000..c120721
--- /dev/null
+++ b/tests/integration/helper.py
@@ -0,0 +1,56 @@
+import io
+import os
+import shutil
+
+from ..helper import cd
+
+__all__ = ["add_post_without_text", "append_config", "cd", "create_simple_post", "patch_config"]
+
+
+def add_post_without_text(directory):
+ """Add a post without text."""
+ # File for Issue #374 (empty post text)
+ create_simple_post(directory, "empty.txt", "foobar")
+
+
+def create_simple_post(directory, filename, title_slug, text='', date='2013-03-06 19:08:15'):
+ """Create a simple post in a given directory."""
+ path = os.path.join(directory, filename)
+ text_processed = '\n' + text if text else ''
+ with io.open(path, "w+", encoding="utf8") as outf:
+ outf.write(
+ """
+.. title: {0}
+.. slug: {0}
+.. date: {1}
+{2}""".format(title_slug, date, text_processed)
+ )
+
+
+def copy_example_post(destination_dir):
+ """Copy a modified version of the example post into the site."""
+ test_dir = os.path.abspath(os.path.dirname(__file__))
+ source_file = os.path.join(test_dir, "..", "data", "1-nolinks.rst")
+ destination = os.path.join(destination_dir, "1.rst")
+ shutil.copy(source_file, destination)
+
+
+def append_config(config_dir, appendix):
+ """Append text to the config file."""
+ config_path = os.path.join(config_dir, "conf.py")
+ with io.open(config_path, "a", encoding="utf8") as outf:
+ outf.write(appendix)
+
+
+def patch_config(config_dir, *replacements):
+ """Patch the config file with new values (find and replace)."""
+ config_path = os.path.join(config_dir, "conf.py")
+ with io.open(config_path, "r", encoding="utf-8") as inf:
+ data = inf.read()
+
+ for old, new in replacements:
+ data = data.replace(old, new)
+
+ with io.open(config_path, "w+", encoding="utf8") as outf:
+ outf.write(data)
+ outf.flush()
diff --git a/tests/integration/test_archive_full.py b/tests/integration/test_archive_full.py
new file mode 100644
index 0000000..70d9504
--- /dev/null
+++ b/tests/integration/test_archive_full.py
@@ -0,0 +1,44 @@
+"""Check that full archives build and are correct."""
+
+import os
+
+import pytest
+
+from nikola import __main__
+
+from .helper import cd, patch_config
+from .test_demo_build import prepare_demo_site
+from .test_empty_build import ( # NOQA
+ test_avoid_double_slash_in_rss,
+ test_check_files,
+ test_check_links,
+ test_index_in_sitemap,
+)
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ pytest.param(["archive.html"], id="overall"),
+ pytest.param(["2012", "index.html"], id="year"),
+ pytest.param(["2012", "03", "index.html"], id="month"),
+ pytest.param(["2012", "03", "30", "index.html"], id="day"),
+ ],
+)
+def test_full_archive(build, output_dir, path):
+ """Check existance of archive pages"""
+ expected_path = os.path.join(output_dir, *path)
+ assert os.path.isfile(expected_path)
+
+
+@pytest.fixture(scope="module")
+def build(target_dir):
+ """Fill the site with demo content and build it."""
+ prepare_demo_site(target_dir)
+
+ patch_config(
+ target_dir, ("# CREATE_FULL_ARCHIVES = False", "CREATE_FULL_ARCHIVES = True")
+ )
+
+ with cd(target_dir):
+ __main__.main(["build"])
diff --git a/tests/integration/test_archive_per_day.py b/tests/integration/test_archive_per_day.py
new file mode 100644
index 0000000..52578a6
--- /dev/null
+++ b/tests/integration/test_archive_per_day.py
@@ -0,0 +1,36 @@
+"""Check that per-day archives build and are correct."""
+
+import os
+
+import pytest
+
+from nikola import __main__
+
+from .helper import cd, patch_config
+from .test_demo_build import prepare_demo_site
+from .test_empty_build import ( # NOQA
+ test_archive_exists,
+ test_avoid_double_slash_in_rss,
+ test_check_files,
+ test_check_links,
+ test_index_in_sitemap,
+)
+
+
+def test_day_archive(build, output_dir):
+ """See that it builds"""
+ archive = os.path.join(output_dir, "2012", "03", "30", "index.html")
+ assert os.path.isfile(archive)
+
+
+@pytest.fixture(scope="module")
+def build(target_dir):
+ """Fill the site with demo content and build it."""
+ prepare_demo_site(target_dir)
+
+ patch_config(
+ target_dir, ("# CREATE_DAILY_ARCHIVE = False", "CREATE_DAILY_ARCHIVE = True")
+ )
+
+ with cd(target_dir):
+ __main__.main(["build"])
diff --git a/tests/integration/test_archive_per_month.py b/tests/integration/test_archive_per_month.py
new file mode 100644
index 0000000..5f6c94b
--- /dev/null
+++ b/tests/integration/test_archive_per_month.py
@@ -0,0 +1,36 @@
+"""Check that the monthly archives build and are correct."""
+
+import os
+
+import pytest
+
+from nikola import __main__
+
+from .helper import cd, patch_config
+from .test_demo_build import prepare_demo_site
+from .test_empty_build import ( # NOQA
+ test_archive_exists,
+ test_avoid_double_slash_in_rss,
+ test_check_files,
+ test_check_links,
+ test_index_in_sitemap,
+)
+
+
+def test_monthly_archive(build, output_dir):
+ """Check that the monthly archive is build."""
+ assert os.path.isfile(os.path.join(output_dir, "2012", "03", "index.html"))
+
+
+@pytest.fixture(scope="module")
+def build(target_dir):
+ """Fill the site with demo content and build it."""
+ prepare_demo_site(target_dir)
+
+ patch_config(
+ target_dir,
+ ("# CREATE_MONTHLY_ARCHIVE = False", "CREATE_MONTHLY_ARCHIVE = True"),
+ )
+
+ with cd(target_dir):
+ __main__.main(["build"])
diff --git a/tests/integration/test_building_in_subdir.py b/tests/integration/test_building_in_subdir.py
new file mode 100644
index 0000000..37b2aae
--- /dev/null
+++ b/tests/integration/test_building_in_subdir.py
@@ -0,0 +1,32 @@
+"""
+Check that running nikola from subdir works.
+
+Check whether build works from posts/
+"""
+
+import os
+
+import pytest
+
+from nikola import __main__
+
+from .helper import cd
+from .test_demo_build import prepare_demo_site
+from .test_empty_build import ( # NOQA
+ test_archive_exists,
+ test_avoid_double_slash_in_rss,
+ test_check_files,
+ test_check_links,
+ test_index_in_sitemap,
+)
+
+
+@pytest.fixture(scope="module")
+def build(target_dir):
+ """Fill the site with demo content and build it."""
+ prepare_demo_site(target_dir)
+
+ build_dir = os.path.join(target_dir, "posts")
+
+ with cd(build_dir):
+ __main__.main(["build"])
diff --git a/tests/integration/test_category_destpath.py b/tests/integration/test_category_destpath.py
new file mode 100644
index 0000000..9defcd3
--- /dev/null
+++ b/tests/integration/test_category_destpath.py
@@ -0,0 +1,88 @@
+"""Test if category destpath indexes avoid pages."""
+
+import os
+
+import pytest
+
+import nikola.plugins.command.init
+from nikola import __main__
+from nikola.utils import makedirs
+
+from .helper import append_config, cd, create_simple_post
+from .test_empty_build import ( # NOQA
+ test_archive_exists,
+ test_avoid_double_slash_in_rss,
+ test_check_files,
+ test_check_links,
+ test_index_in_sitemap,
+)
+
+
+def test_destpath_with_avoidance(build, output_dir):
+ """Test destpath categories page generation and avoidance."""
+
+ def _make_output_path(dir, name):
+ """Make a file path to the output."""
+ return os.path.join(dir, name + ".html")
+
+ cat1 = os.path.join(output_dir, "posts", "cat1")
+ cat2 = os.path.join(output_dir, "posts", "cat2")
+
+ index1 = _make_output_path(cat1, "index")
+ index2 = _make_output_path(cat2, "index")
+
+ # Do all files exist?
+ assert os.path.isfile(index1)
+ assert os.path.isfile(index2)
+
+ # Are their contents correct?
+ with open(index1, "r", encoding="utf-8") as fh:
+ page = fh.read()
+
+ assert "Posts about cat1" in page
+ assert "test-destpath-p1" in page
+ assert "test-destpath-p2" in page
+ assert "test-destpath-p3" not in page
+
+ with open(index2, "r", encoding="utf-8") as fh:
+ page = fh.read()
+
+ assert "Posts about cat2" not in page
+ assert "This is a post that conflicts with cat2." in page
+
+
+@pytest.fixture(scope="module")
+def build(target_dir):
+ """
+ Add subdirectories and create a post in category "cat1" and a page
+ with the same URL as the category index (created via destpaths).
+ """
+ init_command = nikola.plugins.command.init.CommandInit()
+ init_command.create_empty_site(target_dir)
+ init_command.create_configuration(target_dir)
+
+ posts = os.path.join(target_dir, "posts")
+ cat1 = os.path.join(posts, "cat1")
+ cat2 = os.path.join(posts, "cat2")
+
+ makedirs(cat1)
+ makedirs(cat2)
+
+ create_simple_post(cat1, "p1.txt", "test-destpath-p1", "This is a post in cat1.")
+ create_simple_post(cat1, "p2.txt", "test-destpath-p2", "This is a post in cat1.")
+ create_simple_post(cat2, "p3.txt", "test-destpath-p3", "This is a post in cat2.")
+ create_simple_post(posts, "cat2.txt", "cat2", "This is a post that conflicts with cat2.")
+
+ append_config(
+ target_dir,
+ """
+PRETTY_URLS = True
+CATEGORY_ALLOW_HIERARCHIES = True
+CATEGORY_DESTPATH_AS_DEFAULT = True
+CATEGORY_DESTPATH_TRIM_PREFIX = True
+CATEGORY_PAGES_FOLLOW_DESTPATH = True
+""",
+ )
+
+ with cd(target_dir):
+ __main__.main(["build"])
diff --git a/tests/integration/test_check_absolute_subfolder.py b/tests/integration/test_check_absolute_subfolder.py
new file mode 100644
index 0000000..86af065
--- /dev/null
+++ b/tests/integration/test_check_absolute_subfolder.py
@@ -0,0 +1,50 @@
+"""
+Validate links in a site which is:
+
+* built in URL_TYPE="absolute"
+* deployable to a subfolder (BASE_URL="https://example.com/foo/")
+"""
+
+import io
+import os
+
+import pytest
+
+from nikola import __main__
+
+from .helper import cd, patch_config
+from .test_demo_build import prepare_demo_site
+from .test_empty_build import ( # NOQA
+ test_archive_exists,
+ test_avoid_double_slash_in_rss,
+ test_check_files,
+ test_check_links,
+)
+
+
+def test_index_in_sitemap(build, output_dir):
+ """
+ Test that the correct path is in sitemap, and not the wrong one.
+
+ The correct path ends in /foo/ because this is where we deploy to.
+ """
+ sitemap_path = os.path.join(output_dir, "sitemap.xml")
+ with io.open(sitemap_path, "r", encoding="utf8") as inf:
+ sitemap_data = inf.read()
+
+ assert "<loc>https://example.com/foo/</loc>" in sitemap_data
+
+
+@pytest.fixture(scope="module")
+def build(target_dir):
+ """Fill the site with demo content and build it."""
+ prepare_demo_site(target_dir)
+
+ patch_config(
+ target_dir,
+ ('SITE_URL = "https://example.com/"', 'SITE_URL = "https://example.com/foo/"'),
+ ("# URL_TYPE = 'rel_path'", "URL_TYPE = 'absolute'"),
+ )
+
+ with cd(target_dir):
+ __main__.main(["build"])
diff --git a/tests/integration/test_check_failure.py b/tests/integration/test_check_failure.py
new file mode 100644
index 0000000..08e9447
--- /dev/null
+++ b/tests/integration/test_check_failure.py
@@ -0,0 +1,47 @@
+"""
+The demo build should pass 'nikola check' and fail with missing files.
+
+This tests the red path (failures) for the `check` command.
+Green path tests (working as expected) can be found in `test_demo_build`.
+"""
+
+import io
+import os
+
+import pytest
+
+from nikola import __main__
+
+from .helper import cd
+from .test_demo_build import prepare_demo_site
+from .test_empty_build import ( # NOQA
+ test_avoid_double_slash_in_rss,
+ test_index_in_sitemap,
+)
+
+
+def test_check_links_fail(build, output_dir, target_dir):
+ os.unlink(os.path.join(output_dir, "archive.html"))
+
+ with cd(target_dir):
+ result = __main__.main(["check", "-l"])
+ assert result != 0
+
+
+def test_check_files_fail(build, output_dir, target_dir):
+ manually_added_file = os.path.join(output_dir, "foobar")
+ with io.open(manually_added_file, "w+", encoding="utf8") as outf:
+ outf.write("foo")
+
+ with cd(target_dir):
+ result = __main__.main(["check", "-f"])
+ assert result != 0
+
+
+@pytest.fixture(scope="module")
+def build(target_dir):
+ """Fill the site with demo content and build it."""
+ prepare_demo_site(target_dir)
+
+ with cd(target_dir):
+ __main__.main(["build"])
diff --git a/tests/integration/test_check_full_path_subfolder.py b/tests/integration/test_check_full_path_subfolder.py
new file mode 100644
index 0000000..e678016
--- /dev/null
+++ b/tests/integration/test_check_full_path_subfolder.py
@@ -0,0 +1,35 @@
+"""
+Validate links in a site which is:
+
+* built in URL_TYPE="full_path"
+* deployable to a subfolder (BASE_URL="https://example.com/foo/")
+"""
+
+import pytest
+
+from nikola import __main__
+
+from .helper import cd, patch_config
+from .test_check_absolute_subfolder import test_index_in_sitemap # NOQA
+from .test_demo_build import prepare_demo_site
+from .test_empty_build import ( # NOQA
+ test_archive_exists,
+ test_avoid_double_slash_in_rss,
+ test_check_files,
+ test_check_links,
+)
+
+
+@pytest.fixture(scope="module")
+def build(target_dir):
+ """Fill the site with demo content and build it."""
+ prepare_demo_site(target_dir)
+
+ patch_config(
+ target_dir,
+ ('SITE_URL = "https://example.com/"', 'SITE_URL = "https://example.com/foo/"'),
+ ("# URL_TYPE = 'rel_path'", "URL_TYPE = 'full_path'"),
+ )
+
+ with cd(target_dir):
+ __main__.main(["build"])
diff --git a/tests/integration/test_demo_build.py b/tests/integration/test_demo_build.py
new file mode 100644
index 0000000..57a1807
--- /dev/null
+++ b/tests/integration/test_demo_build.py
@@ -0,0 +1,43 @@
+"""
+Test that a default build of a new site based on the demo site works.
+
+This module also is one place where green path tests (working as
+expected) for the `check` command are tested.
+In this case these are tested against the demo site with default
+settings.
+"""
+
+import os
+
+import pytest
+
+import nikola.plugins.command.init
+from nikola import __main__
+
+from .helper import add_post_without_text, cd, copy_example_post
+from .test_empty_build import ( # NOQA
+ test_archive_exists,
+ test_avoid_double_slash_in_rss,
+ test_check_files,
+ test_check_links,
+ test_index_in_sitemap,
+)
+
+
+@pytest.fixture(scope="module")
+def build(target_dir):
+ """Fill the site with demo content and build it."""
+ prepare_demo_site(target_dir)
+
+ with cd(target_dir):
+ __main__.main(["build"])
+
+
+def prepare_demo_site(target_dir):
+ init_command = nikola.plugins.command.init.CommandInit()
+ init_command.copy_sample_site(target_dir)
+ init_command.create_configuration(target_dir)
+
+ posts_dir = os.path.join(target_dir, "posts")
+ copy_example_post(posts_dir)
+ add_post_without_text(posts_dir)
diff --git a/tests/integration/test_empty_build.py b/tests/integration/test_empty_build.py
new file mode 100644
index 0000000..a21bf73
--- /dev/null
+++ b/tests/integration/test_empty_build.py
@@ -0,0 +1,54 @@
+"""Performaning the build of an empty site."""
+
+import io
+import os
+
+import pytest
+
+import nikola.plugins.command.init
+from nikola import __main__
+
+from .helper import cd
+
+
+def test_check_links(build, target_dir):
+ with cd(target_dir):
+ assert __main__.main(["check", "-l"]) is None
+
+
+def test_check_files(build, target_dir):
+ with cd(target_dir):
+ assert __main__.main(["check", "-f"]) is None
+
+
+def test_index_in_sitemap(build, output_dir):
+ sitemap_path = os.path.join(output_dir, "sitemap.xml")
+ with io.open(sitemap_path, "r", encoding="utf8") as inf:
+ sitemap_data = inf.read()
+
+ assert "<loc>https://example.com/</loc>" in sitemap_data
+
+
+def test_avoid_double_slash_in_rss(build, output_dir):
+ rss_path = os.path.join(output_dir, "rss.xml")
+ with io.open(rss_path, "r", encoding="utf8") as inf:
+ rss_data = inf.read()
+
+ assert "https://example.com//" not in rss_data
+
+
+def test_archive_exists(build, output_dir):
+ """Ensure the build did something."""
+ index_path = os.path.join(output_dir, "archive.html")
+ assert os.path.isfile(index_path)
+
+
+@pytest.fixture(scope="module")
+def build(target_dir):
+ """Build the site."""
+ init_command = nikola.plugins.command.init.CommandInit()
+ init_command.create_empty_site(target_dir)
+ init_command.create_configuration(target_dir)
+
+ with cd(target_dir):
+ __main__.main(["build"])
diff --git a/tests/integration/test_future_post.py b/tests/integration/test_future_post.py
new file mode 100644
index 0000000..4645464
--- /dev/null
+++ b/tests/integration/test_future_post.py
@@ -0,0 +1,109 @@
+"""Test a site with future posts."""
+
+import io
+import os
+from datetime import timedelta
+
+import pytest
+
+import nikola
+import nikola.plugins.command.init
+from nikola.utils import current_time
+from nikola import __main__
+
+from .helper import append_config, cd
+from .test_empty_build import ( # NOQA
+ test_archive_exists,
+ test_avoid_double_slash_in_rss,
+ test_check_files,
+ test_check_links,
+ test_index_in_sitemap,
+)
+
+
+def test_future_post_deployment(build, output_dir, target_dir):
+ """ Ensure that the future post is deleted upon deploying. """
+ index_path = os.path.join(output_dir, "index.html")
+ post_in_past = os.path.join(output_dir, "posts", "foo", "index.html")
+ post_in_future = os.path.join(output_dir, "posts", "bar", "index.html")
+
+ assert os.path.isfile(index_path)
+ assert os.path.isfile(post_in_past)
+ assert os.path.isfile(post_in_future)
+
+ # Run deploy command to see if future post is deleted
+ with cd(target_dir):
+ __main__.main(["deploy"])
+
+ assert os.path.isfile(index_path)
+ assert os.path.isfile(post_in_past)
+ assert not os.path.isfile(post_in_future)
+
+
+@pytest.mark.parametrize("filename", ["index.html", "sitemap.xml"])
+def test_future_post_not_in_indexes(build, output_dir, filename):
+ """ Ensure that the future post is not present in the index and sitemap."""
+ filepath = os.path.join(output_dir, filename)
+ assert os.path.isfile(filepath)
+
+ with io.open(filepath, "r", encoding="utf8") as inf:
+ content = inf.read()
+ assert "foo/" in content
+ assert "bar/" not in content
+ assert "baz" not in content
+
+
+@pytest.fixture(scope="module")
+def build(target_dir):
+ """Build the site."""
+ init_command = nikola.plugins.command.init.CommandInit()
+ init_command.create_empty_site(target_dir)
+ init_command.create_configuration(target_dir)
+
+ # Change COMMENT_SYSTEM_ID to not wait for 5 seconds
+ append_config(target_dir, '\nCOMMENT_SYSTEM_ID = "nikolatest"\n')
+
+ def format_datetime(datetime):
+ return datetime.strftime("%Y-%m-%d %H:%M:%S")
+
+ past_datetime = format_datetime(current_time() + timedelta(days=-1))
+ with io.open(
+ os.path.join(target_dir, "posts", "empty1.txt"), "w+", encoding="utf8"
+ ) as past_post:
+ past_post.write(
+ """\
+.. title: foo
+.. slug: foo
+.. date: %s
+"""
+ % past_datetime
+ )
+
+ future_datetime = format_datetime(current_time() + timedelta(days=1))
+ with io.open(
+ os.path.join(target_dir, "posts", "empty2.txt"), "w+", encoding="utf8"
+ ) as future_post:
+ future_post.write(
+ """\
+.. title: bar
+.. slug: bar
+.. date: %s
+"""
+ % future_datetime
+ )
+
+ with io.open(
+ os.path.join(target_dir, "posts", "empty3.txt"), "w+", encoding="utf8"
+ ) as future_post:
+ future_post.write(
+ """\
+.. title: baz
+.. slug: baz
+.. date: %s
+.. pretty_url: false
+"""
+ % future_datetime
+ )
+
+ with cd(target_dir):
+ __main__.main(["build"])
diff --git a/tests/integration/test_page_index_normal_urls.py b/tests/integration/test_page_index_normal_urls.py
new file mode 100644
index 0000000..4dbedfd
--- /dev/null
+++ b/tests/integration/test_page_index_normal_urls.py
@@ -0,0 +1,238 @@
+"""Test if PAGE_INDEX works, with different PRETTY_URLS=False settings."""
+
+import io
+import os
+
+import pytest
+
+import nikola.plugins.command.init
+from nikola import __main__
+from nikola.utils import makedirs
+
+from .helper import append_config, cd
+from .test_empty_build import ( # NOQA
+ test_archive_exists,
+ test_avoid_double_slash_in_rss,
+ test_check_files,
+ test_check_links,
+ test_index_in_sitemap,
+)
+
+
+def get_last_folder_as_id(value):
+ """Use the last part of the directories as test identifier."""
+ if isinstance(value, (tuple,)):
+ return value[-1]
+
+ return value
+
+
+@pytest.mark.parametrize(
+ "dirs, expected_file",
+ [
+ (("pages",), "page0"),
+ (("pages", "subdir1"), "page1"),
+ (("pages", "subdir1"), "page2"),
+ (("pages", "subdir2"), "page3"),
+ (("pages", "subdir3"), "page4"),
+ ],
+ ids=get_last_folder_as_id,
+)
+def test_page_index(build, output_dir, dirs, expected_file, output_path_func):
+ """Test PAGE_INDEX - Do all files exist?"""
+ path_func = output_path_func
+
+ checkdir = os.path.join(output_dir, *dirs)
+
+ assert os.path.isfile(path_func(checkdir, expected_file))
+
+
+@pytest.mark.parametrize(
+ "dirs, expected_index_file",
+ [
+ (("pages",), "index.html"),
+ (("pages", "subdir1"), "index.html"),
+ (("pages", "subdir2"), "index.html"),
+ (("pages", "subdir3"), "index.php"),
+ ],
+ ids=get_last_folder_as_id,
+)
+def test_page_index_in_subdir(build, output_dir, dirs, expected_index_file):
+ """Test PAGE_INDEX - Do index files in subdir exist?"""
+ checkdir = os.path.join(output_dir, *dirs)
+
+ assert os.path.isfile(os.path.join(checkdir, expected_index_file))
+ if expected_index_file == "index.php":
+ assert not os.path.isfile(os.path.join(checkdir, "index.html"))
+
+
+@pytest.fixture(scope="module")
+def output_path_func():
+ def output_path(dir, name):
+ """Make a file path to the output."""
+ return os.path.join(dir, name + ".html")
+
+ return output_path
+
+
+def test_page_index_content_in_pages(build, output_dir):
+ """Do the indexes only contain the pages the should?"""
+ pages = os.path.join(output_dir, "pages")
+
+ with io.open(os.path.join(pages, "index.html"), "r", encoding="utf-8") as fh:
+ pages_index = fh.read()
+
+ assert "Page 0" in pages_index
+ assert "Page 1" not in pages_index
+ assert "Page 2" not in pages_index
+ assert "Page 3" not in pages_index
+ assert "Page 4" not in pages_index
+ assert "This is not the page index" not in pages_index
+
+
+def test_page_index_content_in_subdir1(build, output_dir):
+ """Do the indexes only contain the pages the should?"""
+ subdir1 = os.path.join(output_dir, "pages", "subdir1")
+
+ with io.open(os.path.join(subdir1, "index.html"), "r", encoding="utf-8") as fh:
+ subdir1_index = fh.read()
+
+ assert "Page 0" not in subdir1_index
+ assert "Page 1" in subdir1_index
+ assert "Page 2" in subdir1_index
+ assert "Page 3" not in subdir1_index
+ assert "Page 4" not in subdir1_index
+ assert "This is not the page index" not in subdir1_index
+
+
+def test_page_index_content_in_subdir2(build, output_dir):
+ """Do the indexes only contain the pages the should?"""
+ subdir2 = os.path.join(output_dir, "pages", "subdir2")
+
+ with io.open(os.path.join(subdir2, "index.html"), "r", encoding="utf-8") as fh:
+ subdir2_index = fh.read()
+
+ assert "Page 0" not in subdir2_index
+ assert "Page 1" not in subdir2_index
+ assert "Page 2" not in subdir2_index
+ assert "Page 3" not in subdir2_index
+ assert "Page 4" not in subdir2_index
+ assert "This is not the page index." in subdir2_index
+
+
+def test_page_index_content_in_subdir3(build, output_dir):
+ """Do the indexes only contain the pages the should?"""
+ subdir3 = os.path.join(output_dir, "pages", "subdir3")
+
+ with io.open(os.path.join(subdir3, "index.php"), "r", encoding="utf-8") as fh:
+ subdir3_index = fh.read()
+
+ assert "Page 0" not in subdir3_index
+ assert "Page 1" not in subdir3_index
+ assert "Page 2" not in subdir3_index
+ assert "Page 3" not in subdir3_index
+ assert "Page 4" not in subdir3_index
+ assert "This is not the page index either." in subdir3_index
+
+
+@pytest.fixture(scope="module")
+def build(target_dir):
+ """Build the site."""
+ init_command = nikola.plugins.command.init.CommandInit()
+ init_command.create_empty_site(target_dir)
+ init_command.create_configuration(target_dir)
+
+ create_pages(target_dir)
+
+ append_config(
+ target_dir,
+ """
+PAGE_INDEX = True
+PRETTY_URLS = False
+PAGES = PAGES + (('pages/*.php', 'pages', 'page.tmpl'),)
+""",
+ )
+
+ with cd(target_dir):
+ __main__.main(["build"])
+
+
+def create_pages(target_dir):
+ pages = os.path.join(target_dir, "pages")
+ subdir1 = os.path.join(target_dir, "pages", "subdir1")
+ subdir2 = os.path.join(target_dir, "pages", "subdir2")
+ subdir3 = os.path.join(target_dir, "pages", "subdir3")
+
+ makedirs(subdir1)
+ makedirs(subdir2)
+ makedirs(subdir3)
+
+ with io.open(os.path.join(pages, "page0.txt"), "w+", encoding="utf8") as outf:
+ outf.write(
+ """\
+.. title: Page 0
+.. slug: page0
+
+This is page 0.
+"""
+ )
+
+ with io.open(os.path.join(subdir1, "page1.txt"), "w+", encoding="utf8") as outf:
+ outf.write(
+ """\
+.. title: Page 1
+.. slug: page1
+
+This is page 1.
+"""
+ )
+
+ with io.open(os.path.join(subdir1, "page2.txt"), "w+", encoding="utf8") as outf:
+ outf.write(
+ """\
+.. title: Page 2
+.. slug: page2
+
+This is page 2.
+"""
+ )
+
+ with io.open(os.path.join(subdir2, "page3.txt"), "w+", encoding="utf8") as outf:
+ outf.write(
+ """\
+.. title: Page 3
+.. slug: page3
+
+This is page 3.
+"""
+ )
+
+ with io.open(os.path.join(subdir2, "foo.txt"), "w+", encoding="utf8") as outf:
+ outf.write(
+ """\
+.. title: Not the page index
+.. slug: index
+
+This is not the page index.
+"""
+ )
+
+ with io.open(os.path.join(subdir3, "page4.txt"), "w+", encoding="utf8") as outf:
+ outf.write(
+ """\
+.. title: Page 4
+.. slug: page4
+
+This is page 4.
+"""
+ )
+
+ with io.open(os.path.join(subdir3, "bar.php"), "w+", encoding="utf8") as outf:
+ outf.write(
+ """\
+.. title: Still not the page index
+.. slug: index
+
+This is not the page index either.
+"""
+ )
diff --git a/tests/integration/test_page_index_pretty_urls.py b/tests/integration/test_page_index_pretty_urls.py
new file mode 100644
index 0000000..b31680f
--- /dev/null
+++ b/tests/integration/test_page_index_pretty_urls.py
@@ -0,0 +1,57 @@
+"""Test if PAGE_INDEX works, with different PRETTY_URLS=True."""
+
+import os
+
+import pytest
+
+import nikola.plugins.command.init
+from nikola import __main__
+
+from .helper import append_config, cd
+from .test_empty_build import ( # NOQA
+ test_archive_exists,
+ test_avoid_double_slash_in_rss,
+ test_check_files,
+ test_check_links,
+ test_index_in_sitemap,
+)
+from .test_page_index_normal_urls import create_pages
+from .test_page_index_normal_urls import ( # NOQA
+ test_page_index,
+ test_page_index_in_subdir,
+ test_page_index_content_in_pages,
+ test_page_index_content_in_subdir1,
+ test_page_index_content_in_subdir2,
+ test_page_index_content_in_subdir3,
+)
+
+
+@pytest.fixture(scope="module")
+def output_path_func():
+ def output_path(dir, name):
+ """Make a file path to the output."""
+ return os.path.join(dir, name + "/index.html")
+
+ return output_path
+
+
+@pytest.fixture(scope="module")
+def build(target_dir):
+ """Build the site."""
+ init_command = nikola.plugins.command.init.CommandInit()
+ init_command.create_empty_site(target_dir)
+ init_command.create_configuration(target_dir)
+
+ create_pages(target_dir)
+
+ append_config(
+ target_dir,
+ """
+PAGE_INDEX = True
+PRETTY_URLS = True
+PAGES = PAGES + (('pages/*.php', 'pages', 'page.tmpl'),)
+""",
+ )
+
+ with cd(target_dir):
+ __main__.main(["build"])
diff --git a/tests/integration/test_redirection.py b/tests/integration/test_redirection.py
new file mode 100644
index 0000000..323740d
--- /dev/null
+++ b/tests/integration/test_redirection.py
@@ -0,0 +1,106 @@
+"""
+Check REDIRECTIONS.
+
+This module tests absolute, external and relative redirects.
+Each of the different redirect types is specified in the config and
+then tested by at least one test."""
+
+import io
+import os
+
+import pytest
+
+import nikola.plugins.command.init
+from nikola import __main__
+
+from .helper import append_config, cd
+from .test_demo_build import prepare_demo_site
+from .test_empty_build import ( # NOQA
+ test_archive_exists,
+ test_avoid_double_slash_in_rss,
+ test_check_files,
+ test_check_links,
+ test_index_in_sitemap,
+)
+
+
+def test_absolute_redirection(build, output_dir):
+ abs_source = os.path.join(output_dir, "redirects", "absolute_source.html")
+ assert os.path.exists(abs_source)
+
+ abs_destination = os.path.join(output_dir, "posts", "absolute.html")
+ assert os.path.exists(abs_destination)
+
+ with open(abs_destination) as abs_destination_fd:
+ abs_destination_content = abs_destination_fd.read()
+
+ redirect_tag = '<meta http-equiv="refresh" content="0; url=/redirects/absolute_source.html">'
+ assert redirect_tag in abs_destination_content
+
+ with open(abs_source) as abs_source_fd:
+ absolute_source_content = abs_source_fd.read()
+
+ assert absolute_source_content == "absolute"
+
+
+def test_external_redirection(build, output_dir):
+ ext_link = os.path.join(output_dir, "external.html")
+
+ assert os.path.exists(ext_link)
+ with open(ext_link) as ext_link_fd:
+ ext_link_content = ext_link_fd.read()
+
+ redirect_tag = '<meta http-equiv="refresh" content="0; url=http://www.example.com/">'
+ assert redirect_tag in ext_link_content
+
+
+def test_relative_redirection(build, output_dir):
+ rel_destination = os.path.join(output_dir, "relative.html")
+ assert os.path.exists(rel_destination)
+ rel_source = os.path.join(output_dir, "redirects", "rel_src.html")
+ assert os.path.exists(rel_source)
+
+ with open(rel_destination) as rel_destination_fd:
+ rel_destination_content = rel_destination_fd.read()
+
+ redirect_tag = '<meta http-equiv="refresh" content="0; url=redirects/rel_src.html">'
+ assert redirect_tag in rel_destination_content
+
+ with open(rel_source) as rel_source_fd:
+ rel_source_content = rel_source_fd.read()
+
+ assert rel_source_content == "relative"
+
+
+@pytest.fixture(scope="module")
+def build(target_dir):
+ """Fill the site with demo content and build it."""
+ prepare_demo_site(target_dir)
+
+ redirects_dir = os.path.join(target_dir, "files", "redirects")
+ nikola.utils.makedirs(redirects_dir)
+
+ # Source file for absolute redirect
+ target_path = os.path.join(redirects_dir, "absolute_source.html")
+ with io.open(target_path, "w+", encoding="utf8") as outf:
+ outf.write("absolute")
+
+ # Source file for relative redirect
+ target_path = os.path.join(redirects_dir, "rel_src.html")
+ with io.open(target_path, "w+", encoding="utf8") as outf:
+ outf.write("relative")
+
+ # Configure usage of specific redirects
+ append_config(
+ target_dir,
+ """
+REDIRECTIONS = [
+ ("posts/absolute.html", "/redirects/absolute_source.html"),
+ ("external.html", "http://www.example.com/"),
+ ("relative.html", "redirects/rel_src.html"),
+]
+""",
+ )
+
+ with cd(target_dir):
+ __main__.main(["build"])
diff --git a/tests/integration/test_relative_links.py b/tests/integration/test_relative_links.py
new file mode 100644
index 0000000..3b158cf
--- /dev/null
+++ b/tests/integration/test_relative_links.py
@@ -0,0 +1,60 @@
+"""Check that SITE_URL with a path doesn't break links."""
+
+import io
+import os
+
+import lxml
+import pytest
+
+from nikola import __main__
+
+from .helper import cd, patch_config
+from .test_demo_build import prepare_demo_site
+from .test_empty_build import ( # NOQA
+ test_archive_exists,
+ test_avoid_double_slash_in_rss,
+ test_check_files,
+ test_check_links,
+)
+
+
+def test_relative_links(build, output_dir):
+ """Check that the links in output/index.html are correct"""
+ test_path = os.path.join(output_dir, "index.html")
+
+ with io.open(test_path, "rb") as inf:
+ data = inf.read()
+
+ assert not any(
+ url.startswith("..")
+ for _, _, url, _ in lxml.html.iterlinks(data)
+ if url.endswith("css")
+ )
+
+
+def test_index_in_sitemap(build, output_dir):
+ """Test that the correct path is in sitemap, and not the wrong one."""
+ sitemap_path = os.path.join(output_dir, "sitemap.xml")
+ with io.open(sitemap_path, "r", encoding="utf8") as inf:
+ sitemap_data = inf.read()
+
+ assert "<loc>https://example.com/</loc>" not in sitemap_data
+ assert "<loc>https://example.com/foo/bar/</loc>" in sitemap_data
+
+
+@pytest.fixture(scope="module")
+def build(target_dir):
+ """Fill the site with demo content and build it."""
+ prepare_demo_site(target_dir)
+
+ # Set the SITE_URL to have a path with subfolder
+ patch_config(
+ target_dir,
+ (
+ 'SITE_URL = "https://example.com/"',
+ 'SITE_URL = "https://example.com/foo/bar/"',
+ ),
+ )
+
+ with cd(target_dir):
+ __main__.main(["build"])
diff --git a/tests/integration/test_relative_links_with_pages_in_root.py b/tests/integration/test_relative_links_with_pages_in_root.py
new file mode 100644
index 0000000..16f9d6f
--- /dev/null
+++ b/tests/integration/test_relative_links_with_pages_in_root.py
@@ -0,0 +1,65 @@
+"""Check that dropping pages to the root doesn't break links."""
+
+import io
+import os
+
+import lxml
+import pytest
+
+from nikola import __main__
+
+from .helper import append_config, cd, patch_config
+from .test_demo_build import prepare_demo_site
+from .test_empty_build import ( # NOQA
+ test_archive_exists,
+ test_avoid_double_slash_in_rss,
+ test_check_files,
+)
+
+
+def test_relative_links(build, output_dir):
+ """Check that the links in a page are correct"""
+ test_path = os.path.join(output_dir, "about-nikola.html")
+
+ with io.open(test_path, "rb") as inf:
+ data = inf.read()
+
+ assert not any(
+ url.startswith("..")
+ for _, _, url, _ in lxml.html.iterlinks(data)
+ if url.endswith("css")
+ )
+
+
+def test_index_in_sitemap(build, output_dir):
+ """Test that the correct path is in sitemap, and not the wrong one."""
+ sitemap_path = os.path.join(output_dir, "sitemap.xml")
+ with io.open(sitemap_path, "r", encoding="utf8") as inf:
+ sitemap_data = inf.read()
+
+ assert "<loc>https://example.com/</loc>" not in sitemap_data
+ assert "<loc>https://example.com/blog/index.html</loc>" in sitemap_data
+
+
+@pytest.fixture(scope="module")
+def build(target_dir):
+ """Fill the site with demo content and build it."""
+ prepare_demo_site(target_dir)
+
+ # Configure our pages to reside in the root
+ patch_config(
+ target_dir,
+ ('("pages/*.txt", "pages", "page.tmpl"),', '("pages/*.txt", "", "page.tmpl"),'),
+ ('("pages/*.rst", "pages", "page.tmpl"),', '("pages/*.rst", "", "page.tmpl"),'),
+ ('# INDEX_PATH = ""', 'INDEX_PATH = "blog"'),
+ )
+ append_config(
+ target_dir,
+ """
+PRETTY_URLS = False
+STRIP_INDEXES = False
+""",
+ )
+
+ with cd(target_dir):
+ __main__.main(["build"])
diff --git a/tests/integration/test_repeated_posts_setting.py b/tests/integration/test_repeated_posts_setting.py
new file mode 100644
index 0000000..2a03338
--- /dev/null
+++ b/tests/integration/test_repeated_posts_setting.py
@@ -0,0 +1,36 @@
+"""
+Duplicate POSTS in settings.
+
+Should not read each post twice, which causes conflicts.
+"""
+
+import pytest
+
+from nikola import __main__
+
+from .helper import append_config, cd
+from .test_demo_build import prepare_demo_site
+from .test_empty_build import ( # NOQA
+ test_archive_exists,
+ test_avoid_double_slash_in_rss,
+ test_check_files,
+ test_check_links,
+ test_index_in_sitemap,
+)
+
+
+@pytest.fixture(scope="module")
+def build(target_dir):
+ """Fill the site with demo content and build it."""
+ prepare_demo_site(target_dir)
+
+ append_config(
+ target_dir,
+ """
+POSTS = (("posts/*.txt", "posts", "post.tmpl"),
+ ("posts/*.txt", "posts", "post.tmpl"))
+""",
+ )
+
+ with cd(target_dir):
+ __main__.main(["build"])
diff --git a/tests/integration/test_translated_content.py b/tests/integration/test_translated_content.py
new file mode 100644
index 0000000..9d1338f
--- /dev/null
+++ b/tests/integration/test_translated_content.py
@@ -0,0 +1,62 @@
+"""
+Test a site with translated content.
+
+Do not test titles as we remove the translation.
+"""
+
+import io
+import os
+import shutil
+
+import lxml.html
+import pytest
+
+import nikola.plugins.command.init
+from nikola import __main__
+
+from .helper import cd
+from .test_empty_build import ( # NOQA
+ test_archive_exists,
+ test_avoid_double_slash_in_rss,
+ test_check_files,
+ test_check_links,
+ test_index_in_sitemap,
+)
+
+
+def test_translated_titles(build, output_dir, other_locale):
+ """Check that translated title is picked up."""
+ normal_file = os.path.join(output_dir, "pages", "1", "index.html")
+ translated_file = os.path.join(output_dir, other_locale, "pages", "1", "index.html")
+
+ # Files should be created
+ assert os.path.isfile(normal_file)
+ assert os.path.isfile(translated_file)
+
+ # And now let's check the titles
+ with io.open(normal_file, "r", encoding="utf8") as inf:
+ doc = lxml.html.parse(inf)
+ assert doc.find("//title").text == "Foo | Demo Site"
+
+ with io.open(translated_file, "r", encoding="utf8") as inf:
+ doc = lxml.html.parse(inf)
+ assert doc.find("//title").text == "Bar | Demo Site"
+
+
+@pytest.fixture(scope="module")
+def build(target_dir, test_dir):
+ """Build the site."""
+ init_command = nikola.plugins.command.init.CommandInit()
+ init_command.create_empty_site(target_dir)
+ init_command.create_configuration(target_dir)
+
+ src = os.path.join(test_dir, "..", "data", "translated_titles")
+ for root, dirs, files in os.walk(src):
+ for src_name in files:
+ rel_dir = os.path.relpath(root, src)
+ dst_file = os.path.join(target_dir, rel_dir, src_name)
+ src_file = os.path.join(root, src_name)
+ shutil.copy2(src_file, dst_file)
+
+ with cd(target_dir):
+ __main__.main(["build"])
diff --git a/tests/integration/test_translated_content_secondary_language.py b/tests/integration/test_translated_content_secondary_language.py
new file mode 100644
index 0000000..f826d1d
--- /dev/null
+++ b/tests/integration/test_translated_content_secondary_language.py
@@ -0,0 +1,40 @@
+"""Make sure posts only in secondary languages work."""
+
+import os
+import shutil
+
+import pytest
+
+import nikola.plugins.command.init
+from nikola import __main__
+
+from .helper import cd
+from .test_empty_build import ( # NOQA
+ test_archive_exists,
+ test_avoid_double_slash_in_rss,
+ test_check_files,
+ test_check_links,
+ test_index_in_sitemap,
+)
+
+
+@pytest.fixture(scope="module")
+def build(target_dir, test_dir):
+ """Build the site."""
+ init_command = nikola.plugins.command.init.CommandInit()
+ init_command.create_empty_site(target_dir)
+ init_command.create_configuration(target_dir)
+
+ src = os.path.join(test_dir, "..", "data", "translated_titles")
+ for root, dirs, files in os.walk(src):
+ for src_name in files:
+ if src_name == "1.txt": # English post
+ continue
+
+ rel_dir = os.path.relpath(root, src)
+ dst_file = os.path.join(target_dir, rel_dir, src_name)
+ src_file = os.path.join(root, src_name)
+ shutil.copy2(src_file, dst_file)
+
+ with cd(target_dir):
+ __main__.main(["build"])
diff --git a/tests/integration/test_translation_patterns.py b/tests/integration/test_translation_patterns.py
new file mode 100644
index 0000000..6f77960
--- /dev/null
+++ b/tests/integration/test_translation_patterns.py
@@ -0,0 +1,55 @@
+"""Check that the path.lang.ext TRANSLATIONS_PATTERN works too"""
+
+import os
+import shutil
+
+import pytest
+
+import nikola.plugins.command.init
+from nikola import __main__
+
+from .helper import cd, patch_config
+from .test_empty_build import ( # NOQA
+ test_archive_exists,
+ test_avoid_double_slash_in_rss,
+ test_check_files,
+ test_check_links,
+ test_index_in_sitemap,
+)
+from .test_translated_content import test_translated_titles # NOQA
+
+
+@pytest.fixture(scope="module")
+def build(target_dir, test_dir, other_locale):
+ """
+ Build the site.
+
+ Set the TRANSLATIONS_PATTERN to the old v6 default.
+ """
+ init_command = nikola.plugins.command.init.CommandInit()
+ init_command.create_empty_site(target_dir)
+ init_command.create_configuration(target_dir)
+
+ src = os.path.join(test_dir, "..", "data", "translated_titles")
+ for root, dirs, files in os.walk(src):
+ for src_name in files:
+ rel_dir = os.path.relpath(root, src)
+ dst_file = os.path.join(target_dir, rel_dir, src_name)
+ src_file = os.path.join(root, src_name)
+ shutil.copy2(src_file, dst_file)
+
+ os.rename(
+ os.path.join(target_dir, "pages", "1.%s.txt" % other_locale),
+ os.path.join(target_dir, "pages", "1.txt.%s" % other_locale),
+ )
+
+ patch_config(
+ target_dir,
+ (
+ 'TRANSLATIONS_PATTERN = "{path}.{lang}.{ext}"',
+ 'TRANSLATIONS_PATTERN = "{path}.{ext}.{lang}"',
+ ),
+ )
+
+ with cd(target_dir):
+ __main__.main(["build"])
diff --git a/tests/integration/test_wordpress_import.py b/tests/integration/test_wordpress_import.py
new file mode 100644
index 0000000..6d3bfb8
--- /dev/null
+++ b/tests/integration/test_wordpress_import.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+"""
+Testing the wordpress import.
+
+It will do create a new site with the import_wordpress command
+and use that newly created site to make a build.
+"""
+
+import os.path
+from glob import glob
+
+import pytest
+
+from nikola import __main__
+
+from .helper import cd
+from .test_empty_build import ( # NOQA
+ test_archive_exists,
+ test_avoid_double_slash_in_rss,
+ test_check_files,
+)
+
+
+def test_import_created_files(build, target_dir):
+ assert os.path.exists(target_dir)
+ assert os.path.exists(os.path.join(target_dir, "conf.py"))
+
+
+@pytest.mark.parametrize("dirname", ["pages", "posts"])
+def test_filled_directories(build, target_dir, dirname):
+ folder = os.path.join(target_dir, dirname)
+ assert os.path.isdir(folder)
+ content = glob(os.path.join(folder, "**"), recursive=True)
+ assert any(os.path.isfile(element) for element in content)
+
+
+@pytest.fixture(scope="module")
+def build(target_dir, import_file):
+ __main__.main(
+ [
+ "import_wordpress",
+ "--no-downloads",
+ "--output-folder",
+ target_dir,
+ import_file,
+ ]
+ )
+
+ with cd(target_dir):
+ result = __main__.main(["build"])
+ assert not result
+
+
+@pytest.fixture(scope="module")
+def import_file(test_dir):
+ """Path to the Wordpress export file."""
+ return os.path.join(
+ test_dir, "..", "data", "wordpress_import", "wordpress_export_example.xml"
+ )
diff --git a/tests/test_command_import_wordpress.py b/tests/test_command_import_wordpress.py
index c1c7836..c8bd19f 100644
--- a/tests/test_command_import_wordpress.py
+++ b/tests/test_command_import_wordpress.py
@@ -1,234 +1,112 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals, absolute_import
-
import os
+from unittest import mock
-import unittest
-import mock
+import pytest
-import nikola
import nikola.plugins.command.import_wordpress
-from .base import BaseTestCase
-
-
-class BasicCommandImportWordpress(BaseTestCase):
- def setUp(self):
- self.module = nikola.plugins.command.import_wordpress
- self.import_command = self.module.CommandImportWordpress()
- self.import_filename = os.path.abspath(os.path.join(
- os.path.dirname(__file__), 'wordpress_export_example.xml'))
-
- def tearDown(self):
- del self.import_command
- del self.import_filename
-
-
-class TestQTranslateContentSeparation(BasicCommandImportWordpress):
-
- def test_conserves_qtranslate_less_post(self):
- content = """Si vous préférez savoir à qui vous parlez commencez par visiter l'<a title="À propos" href="http://some.blog/about/">À propos</a>.
-
-Quoiqu'il en soit, commentaires, questions et suggestions sont les bienvenues !"""
- content_translations = self.module.separate_qtranslate_content(content)
- self.assertEqual(1, len(content_translations))
- self.assertEqual(content, content_translations[""])
-
- def test_split_a_two_language_post(self):
- content = """<!--:fr-->Si vous préférez savoir à qui vous parlez commencez par visiter l'<a title="À propos" href="http://some.blog/about/">À propos</a>.
-
-Quoiqu'il en soit, commentaires, questions et suggestions sont les bienvenues !
-<!--:--><!--:en-->If you'd like to know who you're talking to, please visit the <a title="À propos" href="http://some.blog/about/">about page</a>.
-
-Comments, questions and suggestions are welcome !
-<!--:-->"""
- content_translations = self.module.separate_qtranslate_content(content)
- self.assertEqual("""Si vous préférez savoir à qui vous parlez commencez par visiter l'<a title="À propos" href="http://some.blog/about/">À propos</a>.
-
-Quoiqu'il en soit, commentaires, questions et suggestions sont les bienvenues !
-""", content_translations["fr"])
- self.assertEqual("""If you'd like to know who you're talking to, please visit the <a title="À propos" href="http://some.blog/about/">about page</a>.
-
-Comments, questions and suggestions are welcome !
-""", content_translations["en"])
-
- def test_split_a_two_language_post_with_teaser(self):
- content = """<!--:fr-->Si vous préférez savoir à qui vous parlez commencez par visiter l'<a title="À propos" href="http://some.blog/about/">À propos</a>.
-
-Quoiqu'il en soit, commentaires, questions et suggestions sont les bienvenues !
-<!--:--><!--:en-->If you'd like to know who you're talking to, please visit the <a title="À propos" href="http://some.blog/about/">about page</a>.
-
-Comments, questions and suggestions are welcome !
-<!--:--><!--more--><!--:fr-->
-Plus de détails ici !
-<!--:--><!--:en-->
-More details here !
-<!--:-->"""
- content_translations = self.module.separate_qtranslate_content(content)
- self.assertEqual("""Si vous préférez savoir à qui vous parlez commencez par visiter l'<a title="À propos" href="http://some.blog/about/">À propos</a>.
-
-Quoiqu'il en soit, commentaires, questions et suggestions sont les bienvenues !
- <!--more--> \n\
-Plus de détails ici !
-""", content_translations["fr"])
- self.assertEqual("""If you'd like to know who you're talking to, please visit the <a title="À propos" href="http://some.blog/about/">about page</a>.
-
-Comments, questions and suggestions are welcome !
- <!--more--> \n\
-More details here !
-""", content_translations["en"])
-
- def test_split_a_two_language_post_with_intermission(self):
- content = """<!--:fr-->Voila voila<!--:-->COMMON<!--:en-->BLA<!--:-->"""
- content_translations = self.module.separate_qtranslate_content(content)
- self.assertEqual("Voila voila COMMON", content_translations["fr"])
- self.assertEqual("COMMON BLA", content_translations["en"])
-
- def test_split_a_two_language_post_with_uneven_repartition(self):
- content = """<!--:fr-->Voila voila<!--:-->COMMON<!--:fr-->MOUF<!--:--><!--:en-->BLA<!--:-->"""
- content_translations = self.module.separate_qtranslate_content(content)
- self.assertEqual("Voila voila COMMON MOUF", content_translations["fr"])
- self.assertEqual("COMMON BLA", content_translations["en"])
-
- def test_split_a_two_language_post_with_uneven_repartition_bis(self):
- content = """<!--:fr-->Voila voila<!--:--><!--:en-->BLA<!--:-->COMMON<!--:fr-->MOUF<!--:-->"""
- content_translations = self.module.separate_qtranslate_content(content)
- self.assertEqual("Voila voila COMMON MOUF", content_translations["fr"])
- self.assertEqual("BLA COMMON", content_translations["en"])
-
-
-class CommandImportWordpressRunTest(BasicCommandImportWordpress):
- def setUp(self):
- super(self.__class__, self).setUp()
- self.data_import = mock.MagicMock()
- self.site_generation = mock.MagicMock()
- self.write_urlmap = mock.MagicMock()
- self.write_configuration = mock.MagicMock()
-
- site_generation_patch = mock.patch('os.system', self.site_generation)
- data_import_patch = mock.patch(
- 'nikola.plugins.command.import_wordpress.CommandImportWordpress.import_posts', self.data_import)
- write_urlmap_patch = mock.patch(
- 'nikola.plugins.command.import_wordpress.CommandImportWordpress.write_urlmap_csv', self.write_urlmap)
- write_configuration_patch = mock.patch(
- 'nikola.plugins.command.import_wordpress.CommandImportWordpress.write_configuration', self.write_configuration)
-
- self.patches = [site_generation_patch, data_import_patch,
- write_urlmap_patch, write_configuration_patch]
- for patch in self.patches:
- patch.start()
-
- def tearDown(self):
- del self.data_import
- del self.site_generation
- del self.write_urlmap
- del self.write_configuration
-
- for patch in self.patches:
- patch.stop()
- del self.patches
-
- super(self.__class__, self).tearDown()
-
- def test_create_import(self):
- valid_import_arguments = (
- dict(options={'output_folder': 'some_folder'},
- args=[self.import_filename]),
- dict(args=[self.import_filename]),
- dict(args=[self.import_filename, 'folder_argument']),
- )
- for arguments in valid_import_arguments:
- self.import_command.execute(**arguments)
-
- self.assertTrue(self.site_generation.called)
- self.assertTrue(self.data_import.called)
- self.assertTrue(self.write_urlmap.called)
- self.assertTrue(self.write_configuration.called)
- self.assertFalse(self.import_command.exclude_drafts)
-
- def test_ignoring_drafts(self):
- valid_import_arguments = (
- dict(options={'exclude_drafts': True}, args=[
- self.import_filename]),
- dict(
- options={'exclude_drafts': True,
- 'output_folder': 'some_folder'},
- args=[self.import_filename]),
- )
- for arguments in valid_import_arguments:
- self.import_command.execute(**arguments)
- self.assertTrue(self.import_command.exclude_drafts)
-
-
-class CommandImportWordpressTest(BasicCommandImportWordpress):
- def test_create_import_work_without_argument(self):
- # Running this without an argument must not fail.
- # It should show the proper usage of the command.
- self.import_command.execute()
-
- def test_populate_context(self):
- channel = self.import_command.get_channel_from_file(
- self.import_filename)
- self.import_command.transform_to_html = False
- self.import_command.use_wordpress_compiler = False
- context = self.import_command.populate_context(channel)
-
- for required_key in ('POSTS', 'PAGES', 'COMPILERS'):
- self.assertTrue(required_key in context)
-
- self.assertEqual('de', context['DEFAULT_LANG'])
- self.assertEqual('Wordpress blog title', context['BLOG_TITLE'])
- self.assertEqual('Nikola test blog ;) - with moré Ümläüts',
- context['BLOG_DESCRIPTION'])
- self.assertEqual('http://some.blog/', context['SITE_URL'])
- self.assertEqual('mail@some.blog', context['BLOG_EMAIL'])
- self.assertEqual('Niko', context['BLOG_AUTHOR'])
-
- def test_importing_posts_and_attachments(self):
- channel = self.import_command.get_channel_from_file(
- self.import_filename)
- self.import_command.base_dir = ''
- self.import_command.output_folder = 'new_site'
- self.import_command.squash_newlines = True
- self.import_command.no_downloads = False
- self.import_command.export_categories_as_categories = False
- self.import_command.export_comments = False
- self.import_command.transform_to_html = False
- self.import_command.use_wordpress_compiler = False
- self.import_command.context = self.import_command.populate_context(
- channel)
-
- # Ensuring clean results
- self.import_command.url_map = {}
- self.module.links = {}
-
- write_metadata = mock.MagicMock()
- write_content = mock.MagicMock()
- write_attachments_info = mock.MagicMock()
- download_mock = mock.MagicMock()
-
- with mock.patch('nikola.plugins.command.import_wordpress.CommandImportWordpress.write_content', write_content):
- with mock.patch('nikola.plugins.command.import_wordpress.CommandImportWordpress.write_metadata', write_metadata):
- with mock.patch('nikola.plugins.command.import_wordpress.CommandImportWordpress.download_url_content_to_file', download_mock):
- with mock.patch('nikola.plugins.command.import_wordpress.CommandImportWordpress.write_attachments_info', write_attachments_info):
- with mock.patch('nikola.plugins.command.import_wordpress.os.makedirs'):
- self.import_command.import_posts(channel)
-
- self.assertTrue(download_mock.called)
- qpath = 'new_site/files/wp-content/uploads/2008/07/arzt_und_pfusch-sick-cover.png'
- download_mock.assert_any_call(
- 'http://some.blog/wp-content/uploads/2008/07/arzt_und_pfusch-sick-cover.png',
- qpath.replace('/', os.sep))
-
- self.assertTrue(write_metadata.called)
- write_metadata.assert_any_call(
- 'new_site/stories/kontakt.meta'.replace('/', os.sep), 'Kontakt',
- 'kontakt', '2009-07-16 20:20:32', '', [], **{'wp-status': 'publish'})
-
- self.assertTrue(write_content.called)
- write_content.assert_any_call('new_site/posts/2007/04/hoert.md'.replace('/', os.sep),
- """An image.
+def test_create_import_work_without_argument(import_command):
+ """
+ Running import command without an argument must not fail.
+ It should show the proper usage of the command.
+ """
+ import_command.execute()
+
+
+@pytest.mark.parametrize(
+ "key, expected_value",
+ [
+ ("DEFAULT_LANG", "de"),
+ ("BLOG_TITLE", "Wordpress blog title"),
+ ("BLOG_DESCRIPTION", "Nikola test blog ;) - with moré Ümläüts"),
+ ("SITE_URL", "http://some.blog/"),
+ ("BLOG_EMAIL", "mail@some.blog"),
+ ("BLOG_AUTHOR", "Niko"),
+ ],
+)
+def test_populate_context(import_command, import_filename, key, expected_value):
+ channel = import_command.get_channel_from_file(import_filename)
+ import_command.html2text = False
+ import_command.transform_to_markdown = False
+ import_command.transform_to_html = False
+ import_command.use_wordpress_compiler = False
+ import_command.translations_pattern = "{path}.{lang}.{ext}"
+ context = import_command.populate_context(channel)
+
+ for required_key in ("POSTS", "PAGES", "COMPILERS"):
+ assert required_key in context
+
+ assert expected_value == context[key]
+
+
+def test_importing_posts_and_attachments(module, import_command, import_filename):
+ channel = import_command.get_channel_from_file(import_filename)
+ import_command.base_dir = ""
+ import_command.output_folder = "new_site"
+ import_command.squash_newlines = True
+ import_command.no_downloads = False
+ import_command.export_categories_as_categories = False
+ import_command.export_comments = False
+ import_command.html2text = False
+ import_command.transform_to_markdown = False
+ import_command.transform_to_html = False
+ import_command.use_wordpress_compiler = False
+ import_command.tag_saniziting_strategy = "first"
+ import_command.separate_qtranslate_content = False
+ import_command.translations_pattern = "{path}.{lang}.{ext}"
+
+ import_command.context = import_command.populate_context(channel)
+
+ # Ensuring clean results
+ # assert not import_command.url_map
+ assert not module.links
+ import_command.url_map = {}
+
+ write_metadata = mock.MagicMock()
+ write_content = mock.MagicMock()
+ write_attachments_info = mock.MagicMock()
+ download_mock = mock.MagicMock()
+
+ with mock.patch(
+ "nikola.plugins.command.import_wordpress.CommandImportWordpress.write_content",
+ write_content,
+ ), mock.patch(
+ "nikola.plugins.command.import_wordpress.CommandImportWordpress.write_metadata",
+ write_metadata,
+ ), mock.patch(
+ "nikola.plugins.command.import_wordpress.CommandImportWordpress.download_url_content_to_file",
+ download_mock,
+ ), mock.patch(
+ "nikola.plugins.command.import_wordpress.CommandImportWordpress.write_attachments_info",
+ write_attachments_info,
+ ), mock.patch(
+ "nikola.plugins.command.import_wordpress.os.makedirs"
+ ):
+ import_command.import_posts(channel)
+
+ assert download_mock.called
+ qpath = "new_site/files/wp-content/uploads/2008/07/arzt_und_pfusch-sick-cover.png"
+ download_mock.assert_any_call(
+ "http://some.blog/wp-content/uploads/2008/07/arzt_und_pfusch-sick-cover.png",
+ qpath.replace("/", os.sep),
+ )
+
+ assert write_metadata.called
+ write_metadata.assert_any_call(
+ "new_site/pages/kontakt.meta".replace("/", os.sep),
+ "Kontakt",
+ "kontakt",
+ "2009-07-16 20:20:32",
+ "",
+ [],
+ **{"wp-status": "publish"}
+ )
+
+ assert write_content.called
+ write_content.assert_any_call(
+ "new_site/posts/2007/04/hoert.md".replace("/", os.sep),
+ """An image.
<img class="size-full wp-image-16" title="caption test" src="http://some.blog/wp-content/uploads/2009/07/caption_test.jpg" alt="caption test" width="739" height="517" />
@@ -237,157 +115,175 @@ Some source code.
```Python
import sys
-
print sys.version
```
The end.
-
-""", True)
-
- self.assertTrue(write_attachments_info.called)
- write_attachments_info.assert_any_call('new_site/posts/2008/07/arzt-und-pfusch-s-i-c-k.attachments.json'.replace('/', os.sep),
- {10: {'wordpress_user_name': 'Niko',
- 'files_meta': [{'width': 300, 'height': 299},
- {'width': b'150', 'size': 'thumbnail', 'height': b'150'}],
- 'excerpt': 'Arzt+Pfusch - S.I.C.K.',
- 'date_utc': '2009-07-16 19:40:37',
- 'content': 'Das Cover von Arzt+Pfusch - S.I.C.K.',
- 'files': ['/wp-content/uploads/2008/07/arzt_und_pfusch-sick-cover.png',
- '/wp-content/uploads/2008/07/arzt_und_pfusch-sick-cover-150x150.png'],
- 'title': 'Arzt+Pfusch - S.I.C.K.'}})
-
- write_content.assert_any_call(
- 'new_site/posts/2008/07/arzt-und-pfusch-s-i-c-k.md'.replace('/', os.sep),
- '''<img class="size-full wp-image-10 alignright" title="Arzt+Pfusch - S.I.C.K." src="http://some.blog/wp-content/uploads/2008/07/arzt_und_pfusch-sick-cover.png" alt="Arzt+Pfusch - S.I.C.K." width="210" height="209" />Arzt+Pfusch - S.I.C.K.Gerade bin ich \xfcber das Album <em>S.I.C.K</em> von <a title="Arzt+Pfusch" href="http://www.arztpfusch.com/" target="_blank">Arzt+Pfusch</a> gestolpert, welches Arzt+Pfusch zum Download f\xfcr lau anbieten. Das Album steht unter einer Creative Commons <a href="http://creativecommons.org/licenses/by-nc-nd/3.0/de/">BY-NC-ND</a>-Lizenz.
-
-Die Ladung <em>noisebmstupidevildustrial</em> gibts als MP3s mit <a href="http://www.archive.org/download/dmp005/dmp005_64kb_mp3.zip">64kbps</a> und <a href="http://www.archive.org/download/dmp005/dmp005_vbr_mp3.zip">VBR</a>, als Ogg Vorbis und als FLAC (letztere <a href="http://www.archive.org/details/dmp005">hier</a>). <a href="http://www.archive.org/download/dmp005/dmp005-artwork.zip">Artwork</a> und <a href="http://www.archive.org/download/dmp005/dmp005-lyrics.txt">Lyrics</a> gibts nochmal einzeln zum Download.''', True)
- write_content.assert_any_call(
- 'new_site/stories/kontakt.md'.replace('/', os.sep), """<h1>Datenschutz</h1>
-
+""",
+ True,
+ )
+
+ assert write_attachments_info.called
+ write_attachments_info.assert_any_call(
+ "new_site/posts/2008/07/arzt-und-pfusch-s-i-c-k.attachments.json".replace(
+ "/", os.sep
+ ),
+ {
+ 10: {
+ "wordpress_user_name": "Niko",
+ "files_meta": [
+ {"width": 300, "height": 299},
+ {"width": 150, "size": "thumbnail", "height": 150},
+ ],
+ "excerpt": "Arzt+Pfusch - S.I.C.K.",
+ "date_utc": "2009-07-16 19:40:37",
+ "content": "Das Cover von Arzt+Pfusch - S.I.C.K.",
+ "files": [
+ "/wp-content/uploads/2008/07/arzt_und_pfusch-sick-cover.png",
+ "/wp-content/uploads/2008/07/arzt_und_pfusch-sick-cover-150x150.png",
+ ],
+ "title": "Arzt+Pfusch - S.I.C.K.",
+ }
+ },
+ )
+
+ write_content.assert_any_call(
+ "new_site/posts/2008/07/arzt-und-pfusch-s-i-c-k.md".replace("/", os.sep),
+ """<img class="size-full wp-image-10 alignright" title="Arzt+Pfusch - S.I.C.K." src="http://some.blog/wp-content/uploads/2008/07/arzt_und_pfusch-sick-cover.png" alt="Arzt+Pfusch - S.I.C.K." width="210" height="209" />Arzt+Pfusch - S.I.C.K.Gerade bin ich \xfcber das Album <em>S.I.C.K</em> von <a title="Arzt+Pfusch" href="http://www.arztpfusch.com/" target="_blank">Arzt+Pfusch</a> gestolpert, welches Arzt+Pfusch zum Download f\xfcr lau anbieten. Das Album steht unter einer Creative Commons <a href="http://creativecommons.org/licenses/by-nc-nd/3.0/de/">BY-NC-ND</a>-Lizenz.
+Die Ladung <em>noisebmstupidevildustrial</em> gibts als MP3s mit <a href="http://www.archive.org/download/dmp005/dmp005_64kb_mp3.zip">64kbps</a> und <a href="http://www.archive.org/download/dmp005/dmp005_vbr_mp3.zip">VBR</a>, als Ogg Vorbis und als FLAC (letztere <a href="http://www.archive.org/details/dmp005">hier</a>). <a href="http://www.archive.org/download/dmp005/dmp005-artwork.zip">Artwork</a> und <a href="http://www.archive.org/download/dmp005/dmp005-lyrics.txt">Lyrics</a> gibts nochmal einzeln zum Download.""",
+ True,
+ )
+ write_content.assert_any_call(
+ "new_site/pages/kontakt.md".replace("/", os.sep),
+ """<h1>Datenschutz</h1>
Ich erhebe und speichere automatisch in meine Server Log Files Informationen, die dein Browser an mich \xfcbermittelt. Dies sind:
-
<ul>
-
<li>Browsertyp und -version</li>
-
<li>verwendetes Betriebssystem</li>
-
<li>Referrer URL (die zuvor besuchte Seite)</li>
-
<li>IP Adresse des zugreifenden Rechners</li>
-
<li>Uhrzeit der Serveranfrage.</li>
-
</ul>
-
-Diese Daten sind f\xfcr mich nicht bestimmten Personen zuordenbar. Eine Zusammenf\xfchrung dieser Daten mit anderen Datenquellen wird nicht vorgenommen, die Daten werden einzig zu statistischen Zwecken erhoben.""", True)
-
- self.assertTrue(len(self.import_command.url_map) > 0)
-
- self.assertEqual(
- self.import_command.url_map['http://some.blog/2007/04/hoert/'],
- 'http://some.blog/posts/2007/04/hoert.html')
- self.assertEqual(
- self.import_command.url_map[
- 'http://some.blog/2008/07/arzt-und-pfusch-s-i-c-k/'],
- 'http://some.blog/posts/2008/07/arzt-und-pfusch-s-i-c-k.html')
- self.assertEqual(
- self.import_command.url_map['http://some.blog/kontakt/'],
- 'http://some.blog/stories/kontakt.html')
-
- image_thumbnails = [
- 'http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-64x64.png',
- 'http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-300x175.png',
- 'http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-36x36.png',
- 'http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-24x24.png',
- 'http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-96x96.png',
- 'http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-96x96.png',
- 'http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-48x48.png',
- 'http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-96x96.png',
- 'http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-150x150.png'
- ]
-
- for link in image_thumbnails:
- self.assertTrue(
- link in self.module.links,
- 'No link to "{0}" found in {map}.'.format(
- link,
- map=self.module.links
- )
- )
-
- def test_transforming_content(self):
- """Applying markup conversions to content."""
- transform_code = mock.MagicMock()
- transform_caption = mock.MagicMock()
- transform_newlines = mock.MagicMock()
-
- self.import_command.transform_to_html = False
- self.import_command.use_wordpress_compiler = False
-
- with mock.patch('nikola.plugins.command.import_wordpress.CommandImportWordpress.transform_code', transform_code):
- with mock.patch('nikola.plugins.command.import_wordpress.CommandImportWordpress.transform_caption', transform_caption):
- with mock.patch('nikola.plugins.command.import_wordpress.CommandImportWordpress.transform_multiple_newlines', transform_newlines):
- self.import_command.transform_content("random content", "wp", None)
-
- self.assertTrue(transform_code.called)
- self.assertTrue(transform_caption.called)
- self.assertTrue(transform_newlines.called)
-
- def test_transforming_source_code(self):
- """
- Tests the handling of sourcecode tags.
- """
- content = """Hello World.
+Diese Daten sind f\xfcr mich nicht bestimmten Personen zuordenbar. Eine Zusammenf\xfchrung dieser Daten mit anderen Datenquellen wird nicht vorgenommen, die Daten werden einzig zu statistischen Zwecken erhoben.""",
+ True,
+ )
+
+ assert len(import_command.url_map) > 0
+
+ assert (
+ "http://some.blog/posts/2007/04/hoert.html" ==
+ import_command.url_map["http://some.blog/2007/04/hoert/"]
+ )
+ assert (
+ "http://some.blog/posts/2008/07/arzt-und-pfusch-s-i-c-k.html" ==
+ import_command.url_map["http://some.blog/2008/07/arzt-und-pfusch-s-i-c-k/"]
+ )
+ assert (
+ "http://some.blog/pages/kontakt.html" ==
+ import_command.url_map["http://some.blog/kontakt/"]
+ )
+
+ image_thumbnails = [
+ "http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-64x64.png",
+ "http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-300x175.png",
+ "http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-36x36.png",
+ "http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-24x24.png",
+ "http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-96x96.png",
+ "http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-96x96.png",
+ "http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-48x48.png",
+ "http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-96x96.png",
+ "http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-150x150.png",
+ ]
+
+ for link in image_thumbnails:
+ assert link in module.links
+
+
+def test_transforming_content(import_command):
+ """Applying markup conversions to content."""
+
+ import_command.html2text = False
+ import_command.transform_to_markdown = False
+ import_command.transform_to_html = False
+ import_command.use_wordpress_compiler = False
+ import_command.translations_pattern = "{path}.{lang}.{ext}"
+
+ transform_code = mock.MagicMock()
+ transform_caption = mock.MagicMock()
+ transform_newlines = mock.MagicMock()
+
+ with mock.patch(
+ "nikola.plugins.command.import_wordpress.CommandImportWordpress.transform_code",
+ transform_code,
+ ), mock.patch(
+ "nikola.plugins.command.import_wordpress.CommandImportWordpress.transform_caption",
+ transform_caption,
+ ), mock.patch(
+ "nikola.plugins.command.import_wordpress.CommandImportWordpress.transform_multiple_newlines",
+ transform_newlines,
+ ):
+ import_command.transform_content("random content", "wp", None)
+
+ assert transform_code.called
+ assert transform_caption.called
+ assert transform_newlines.called
+
+
+def test_transforming_source_code(import_command):
+ """
+ Tests the handling of sourcecode tags.
+ """
+ content = """Hello World.
[sourcecode language="Python"]
import sys
print sys.version
[/sourcecode]"""
- content = self.import_command.transform_code(content)
+ content = import_command.transform_code(content)
- self.assertFalse('[/sourcecode]' in content)
- self.assertFalse('[sourcecode language=' in content)
+ assert "[/sourcecode]" not in content
+ assert "[sourcecode language=" not in content
- replaced_content = """Hello World.
+ replaced_content = """Hello World.
```Python
import sys
print sys.version
```"""
- self.assertEqual(content, replaced_content)
+ assert content == replaced_content
+
- def test_transform_caption(self):
- caption = '[caption id="attachment_16" align="alignnone" width="739" caption="beautiful picture"]<img class="size-full wp-image-16" src="http://some.blog/wp-content/uploads/2009/07/caption_test.jpg" alt="beautiful picture" width="739" height="517" />[/caption]'
- transformed_content = self.import_command.transform_caption(caption)
+def test_transform_caption(import_command):
+ caption = '[caption id="attachment_16" align="alignnone" width="739" caption="beautiful picture"]<img class="size-full wp-image-16" src="http://some.blog/wp-content/uploads/2009/07/caption_test.jpg" alt="beautiful picture" width="739" height="517" />[/caption]'
+ transformed_content = import_command.transform_caption(caption)
- expected_content = '<img class="size-full wp-image-16" src="http://some.blog/wp-content/uploads/2009/07/caption_test.jpg" alt="beautiful picture" width="739" height="517" />'
+ expected_content = '<img class="size-full wp-image-16" src="http://some.blog/wp-content/uploads/2009/07/caption_test.jpg" alt="beautiful picture" width="739" height="517" />'
- self.assertEqual(transformed_content, expected_content)
+ assert transformed_content == expected_content
- def test_transform_multiple_captions_in_a_post(self):
- content = """asdasdas
+
+def test_transform_multiple_captions_in_a_post(import_command):
+ content = """asdasdas
[caption id="attachment_16" align="alignnone" width="739" caption="beautiful picture"]<img class="size-full wp-image-16" src="http://some.blog/wp-content/uploads/2009/07/caption_test.jpg" alt="beautiful picture" width="739" height="517" />[/caption]
asdasdas
asdasdas
[caption id="attachment_16" align="alignnone" width="739" caption="beautiful picture"]<img class="size-full wp-image-16" title="pretty" src="http://some.blog/wp-content/uploads/2009/07/caption_test.jpg" alt="beautiful picture" width="739" height="517" />[/caption]
asdasdas"""
- expected_content = """asdasdas
+ expected_content = """asdasdas
<img class="size-full wp-image-16" src="http://some.blog/wp-content/uploads/2009/07/caption_test.jpg" alt="beautiful picture" width="739" height="517" />
asdasdas
asdasdas
<img class="size-full wp-image-16" title="pretty" src="http://some.blog/wp-content/uploads/2009/07/caption_test.jpg" alt="beautiful picture" width="739" height="517" />
asdasdas"""
- self.assertEqual(
- expected_content, self.import_command.transform_caption(content))
+ assert expected_content == import_command.transform_caption(content)
+
- def test_transform_multiple_newlines(self):
- content = """This
+def test_transform_multiple_newlines(import_command):
+ content = """This
has
@@ -400,7 +296,7 @@ newlines.
"""
- expected_content = """This
+ expected_content = """This
has
@@ -409,63 +305,166 @@ way to many
newlines.
"""
- self.import_command.squash_newlines = False
- self.assertEqual(content,
- self.import_command.transform_multiple_newlines(content))
+ import_command.squash_newlines = False
+ assert content == import_command.transform_multiple_newlines(content)
- self.import_command.squash_newlines = True
- self.assertEqual(expected_content,
- self.import_command.transform_multiple_newlines(content))
+ import_command.squash_newlines = True
+ assert expected_content == import_command.transform_multiple_newlines(content)
- def test_transform_caption_with_link_inside(self):
- content = """[caption caption="Fehlermeldung"]<a href="http://some.blog/openttd-missing_sound.png"><img class="size-thumbnail wp-image-551" title="openttd-missing_sound" src="http://some.blog/openttd-missing_sound-150x150.png" alt="Fehlermeldung" /></a>[/caption]"""
- transformed_content = self.import_command.transform_caption(content)
- expected_content = """<a href="http://some.blog/openttd-missing_sound.png"><img class="size-thumbnail wp-image-551" title="openttd-missing_sound" src="http://some.blog/openttd-missing_sound-150x150.png" alt="Fehlermeldung" /></a>"""
- self.assertEqual(expected_content, transformed_content)
+def test_transform_caption_with_link_inside(import_command):
+ content = """[caption caption="Fehlermeldung"]<a href="http://some.blog/openttd-missing_sound.png"><img class="size-thumbnail wp-image-551" title="openttd-missing_sound" src="http://some.blog/openttd-missing_sound-150x150.png" alt="Fehlermeldung" /></a>[/caption]"""
+ transformed_content = import_command.transform_caption(content)
- def test_get_configuration_output_path(self):
- self.import_command.output_folder = 'new_site'
- default_config_path = os.path.join('new_site', 'conf.py')
+ expected_content = """<a href="http://some.blog/openttd-missing_sound.png"><img class="size-thumbnail wp-image-551" title="openttd-missing_sound" src="http://some.blog/openttd-missing_sound-150x150.png" alt="Fehlermeldung" /></a>"""
+ assert expected_content == transformed_content
- self.import_command.import_into_existing_site = False
- self.assertEqual(default_config_path,
- self.import_command.get_configuration_output_path())
- self.import_command.import_into_existing_site = True
- config_path_with_timestamp = self.import_command.get_configuration_output_path(
- )
- self.assertNotEqual(default_config_path, config_path_with_timestamp)
- self.assertTrue(self.import_command.name in config_path_with_timestamp)
+def test_get_configuration_output_path(import_command):
+ import_command.output_folder = "new_site"
+ default_config_path = os.path.join("new_site", "conf.py")
+
+ import_command.import_into_existing_site = False
+ assert default_config_path == import_command.get_configuration_output_path()
+
+ import_command.import_into_existing_site = True
+ config_path_with_timestamp = import_command.get_configuration_output_path()
- def test_write_content_does_not_detroy_text(self):
- content = b"""FOO"""
- open_mock = mock.mock_open()
- with mock.patch('nikola.plugins.basic_import.open', open_mock, create=True):
- self.import_command.write_content('some_file', content)
+ assert default_config_path != config_path_with_timestamp
+ assert import_command.name in config_path_with_timestamp
- open_mock.assert_has_calls([
- mock.call(u'some_file', u'wb+'),
+
+def test_write_content_does_not_detroy_text(import_command):
+ content = b"""FOO"""
+ open_mock = mock.mock_open()
+ with mock.patch("nikola.plugins.basic_import.open", open_mock, create=True):
+ import_command.write_content("some_file", content)
+
+ open_mock.assert_has_calls(
+ [
+ mock.call(u"some_file", u"wb+"),
mock.call().__enter__(),
- mock.call().write(b'<html><body><p>FOO</p></body></html>'),
- mock.call().__exit__(None, None, None)]
- )
+ mock.call().write(b"<html><body><p>FOO</p></body></html>"),
+ mock.call().__exit__(None, None, None),
+ ]
+ )
+
+
+def test_configure_redirections(import_command):
+ """
+ Testing the configuration of the redirections.
+
+ We need to make sure that we have valid sources and target links.
+ """
+ url_map = {"/somewhere/else": "http://foo.bar/posts/somewhereelse.html"}
+
+ redirections = import_command.configure_redirections(url_map)
- def test_configure_redirections(self):
- """
- Testing the configuration of the redirections.
+ assert 1 == len(redirections)
+ assert ("somewhere/else/index.html", "/posts/somewhereelse.html") in redirections
- We need to make sure that we have valid sources and target links.
- """
- url_map = {
- '/somewhere/else': 'http://foo.bar/posts/somewhereelse.html'
- }
- redirections = self.import_command.configure_redirections(url_map)
+@pytest.mark.parametrize(
+ "options, additional_args",
+ [
+ pytest.param(None, None, id="only import filename"),
+ ({"output_folder": "some_folder"}, None),
+ (None, ["folder_argument"]),
+ ],
+)
+def test_create_import(
+ patched_import_command, import_filename, mocks, options, additional_args
+):
+ arguments = {"args": [import_filename]}
+ if options:
+ arguments["options"] = options
+ if additional_args:
+ arguments["args"].extend(additional_args)
- self.assertEqual(1, len(redirections))
- self.assertTrue(('somewhere/else/index.html', '/posts/somewhereelse.html') in redirections)
+ patched_import_command.execute(**arguments)
+ for applied_mock in mocks:
+ assert applied_mock.called
-if __name__ == '__main__':
- unittest.main()
+ assert patched_import_command.exclude_drafts is False
+
+
+@pytest.mark.parametrize(
+ "options",
+ [
+ {"exclude_drafts": True},
+ {"exclude_drafts": True, "output_folder": "some_folder"},
+ ],
+)
+def test_ignoring_drafts_during_import(
+ patched_import_command, import_filename, options
+):
+ arguments = {"options": options, "args": [import_filename]}
+
+ patched_import_command.execute(**arguments)
+ assert patched_import_command.exclude_drafts is True
+
+
+@pytest.fixture
+def import_command(module):
+ command = module.CommandImportWordpress()
+ command.onefile = False
+ return command
+
+
+@pytest.fixture
+def module():
+ return nikola.plugins.command.import_wordpress
+
+
+@pytest.fixture
+def import_filename(test_dir):
+ return os.path.abspath(
+ os.path.join(
+ test_dir, "data", "wordpress_import", "wordpress_export_example.xml"
+ )
+ )
+
+
+@pytest.fixture
+def patched_import_command(import_command, testsite, mocks):
+ """
+ Import command with disabled site generation and various functions mocked.
+ """
+ data_import, site_generation, write_urlmap, write_configuration = mocks
+
+ import_command.site = testsite
+ with mock.patch("os.system", site_generation), mock.patch(
+ "nikola.plugins.command.import_wordpress.CommandImportWordpress.import_posts",
+ data_import,
+ ), mock.patch(
+ "nikola.plugins.command.import_wordpress.CommandImportWordpress.write_urlmap_csv",
+ write_urlmap,
+ ), mock.patch(
+ "nikola.plugins.command.import_wordpress.CommandImportWordpress.write_configuration",
+ write_configuration,
+ ):
+ yield import_command
+
+
+@pytest.fixture
+def testsite():
+ return FakeSite()
+
+
+class FakeSite:
+ def link(self, *args, **kwargs):
+ # We need a link function.
+ # Stubbed because there is nothing done with the results.
+ pass
+
+
+@pytest.fixture
+def mocks():
+ "Mocks to be used in `patched_import_command`"
+ return [
+ mock.MagicMock(name="data_import"),
+ mock.MagicMock(name="site_generation"),
+ mock.MagicMock(name="write_urlmap"),
+ mock.MagicMock(name="write_configuration"),
+ ]
diff --git a/tests/test_command_import_wordpress_translation.py b/tests/test_command_import_wordpress_translation.py
new file mode 100644
index 0000000..6882ba5
--- /dev/null
+++ b/tests/test_command_import_wordpress_translation.py
@@ -0,0 +1,144 @@
+import os
+
+import pytest
+
+from nikola.plugins.command.import_wordpress import (
+ modernize_qtranslate_tags,
+ separate_qtranslate_tagged_langs,
+)
+
+
+def legacy_qtranslate_separate(text):
+ """This method helps keeping the legacy tests covering various
+ corner cases, but plugged on the newer methods."""
+ text_bytes = text.encode("utf-8")
+ modern_bytes = modernize_qtranslate_tags(text_bytes)
+ modern_text = modern_bytes.decode("utf-8")
+ return separate_qtranslate_tagged_langs(modern_text)
+
+
+@pytest.mark.parametrize(
+ "content, french_translation, english_translation",
+ [
+ pytest.param("[:fr]Voila voila[:en]BLA[:]", "Voila voila", "BLA", id="simple"),
+ pytest.param(
+ "[:fr]Voila voila[:]COMMON[:en]BLA[:]",
+ "Voila voila COMMON",
+ "COMMON BLA",
+ id="pre modern with intermission",
+ ),
+ pytest.param(
+ "<!--:fr-->Voila voila<!--:-->COMMON<!--:en-->BLA<!--:-->",
+ "Voila voila COMMON",
+ "COMMON BLA",
+ id="withintermission",
+ ),
+ pytest.param(
+ "<!--:fr-->Voila voila<!--:-->COMMON<!--:fr-->MOUF<!--:--><!--:en-->BLA<!--:-->",
+ "Voila voila COMMON MOUF",
+ "COMMON BLA",
+ id="with uneven repartition",
+ ),
+ pytest.param(
+ "<!--:fr-->Voila voila<!--:--><!--:en-->BLA<!--:-->COMMON<!--:fr-->MOUF<!--:-->",
+ "Voila voila COMMON MOUF",
+ "BLA COMMON",
+ id="with uneven repartition bis",
+ ),
+ ],
+)
+def test_legacy_split_a_two_language_post(
+ content, french_translation, english_translation
+):
+ content_translations = legacy_qtranslate_separate(content)
+ assert french_translation == content_translations["fr"]
+ assert english_translation == content_translations["en"]
+
+
+def test_conserves_qtranslate_less_post():
+ content = """Si vous préférez savoir à qui vous parlez commencez par visiter l'<a title="À propos" href="http://some.blog/about/">À propos</a>.
+
+Quoiqu'il en soit, commentaires, questions et suggestions sont les bienvenues !"""
+ content_translations = legacy_qtranslate_separate(content)
+ assert 1 == len(content_translations)
+ assert content == content_translations[""]
+
+
+def test_modernize_a_wordpress_export_xml_chunk(test_dir):
+ raw_export_path = os.path.join(
+ test_dir, "data", "wordpress_import", "wordpress_qtranslate_item_raw_export.xml"
+ )
+ with open(raw_export_path, "rb") as raw_xml_chunk_file:
+ content = raw_xml_chunk_file.read()
+
+ output = modernize_qtranslate_tags(content)
+
+ modernized_xml_path = os.path.join(
+ test_dir, "data", "wordpress_import", "wordpress_qtranslate_item_modernized.xml"
+ )
+ with open(modernized_xml_path, "rb") as modernized_chunk_file:
+ expected = modernized_chunk_file.read()
+
+ assert expected == output
+
+
+def test_modernize_qtranslate_tags():
+ content = b"<!--:fr-->Voila voila<!--:-->COMMON<!--:fr-->MOUF<!--:--><!--:en-->BLA<!--:-->"
+ output = modernize_qtranslate_tags(content)
+ assert b"[:fr]Voila voila[:]COMMON[:fr]MOUF[:][:en]BLA[:]" == output
+
+
+def test_split_a_two_language_post():
+ content = """<!--:fr-->Si vous préférez savoir à qui vous parlez commencez par visiter l'<a title="À propos" href="http://some.blog/about/">À propos</a>.
+
+Quoiqu'il en soit, commentaires, questions et suggestions sont les bienvenues !
+<!--:--><!--:en-->If you'd like to know who you're talking to, please visit the <a title="À propos" href="http://some.blog/about/">about page</a>.
+
+Comments, questions and suggestions are welcome !
+<!--:-->"""
+ content_translations = legacy_qtranslate_separate(content)
+
+ assert (
+ content_translations["fr"] == """Si vous préférez savoir à qui vous parlez commencez par visiter l'<a title="À propos" href="http://some.blog/about/">À propos</a>.
+
+Quoiqu'il en soit, commentaires, questions et suggestions sont les bienvenues !
+"""
+ )
+
+ assert (
+ content_translations["en"] == """If you'd like to know who you're talking to, please visit the <a title="À propos" href="http://some.blog/about/">about page</a>.
+
+Comments, questions and suggestions are welcome !
+"""
+ )
+
+
+def test_split_a_two_language_post_with_teaser():
+ content = """<!--:fr-->Si vous préférez savoir à qui vous parlez commencez par visiter l'<a title="À propos" href="http://some.blog/about/">À propos</a>.
+
+Quoiqu'il en soit, commentaires, questions et suggestions sont les bienvenues !
+<!--:--><!--:en-->If you'd like to know who you're talking to, please visit the <a title="À propos" href="http://some.blog/about/">about page</a>.
+
+Comments, questions and suggestions are welcome !
+<!--:--><!--more--><!--:fr-->
+Plus de détails ici !
+<!--:--><!--:en-->
+More details here !
+<!--:-->"""
+ content_translations = legacy_qtranslate_separate(content)
+ assert (
+ content_translations["fr"] == """Si vous préférez savoir à qui vous parlez commencez par visiter l'<a title="À propos" href="http://some.blog/about/">À propos</a>.
+
+Quoiqu'il en soit, commentaires, questions et suggestions sont les bienvenues !
+ <!--more--> \n\
+Plus de détails ici !
+"""
+ )
+ assert (
+ content_translations["en"] == """If you'd like to know who you're talking to, please visit the <a title="À propos" href="http://some.blog/about/">about page</a>.
+
+Comments, questions and suggestions are welcome !
+ <!--more--> \n\
+More details here !
+"""
+ )
diff --git a/tests/test_command_init.py b/tests/test_command_init.py
index 9270497..96fa78e 100644
--- a/tests/test_command_init.py
+++ b/tests/test_command_init.py
@@ -1,104 +1,134 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals, absolute_import
-
-import os
-import sys
-
-import unittest
-import mock
-
-import nikola
-from nikola.plugins.command.init import SAMPLE_CONF
-from nikola.plugins.command.init import format_default_translations_config
-
-
-class CommandInitCallTest(unittest.TestCase):
- def setUp(self):
- self.ask_questions = mock.MagicMock()
- self.copy_sample_site = mock.MagicMock()
- self.create_configuration = mock.MagicMock()
- self.create_empty_site = mock.MagicMock()
- ask_questions_patch = mock.patch(
- 'nikola.plugins.command.init.CommandInit.ask_questions', self.ask_questions)
- copy_sample_site_patch = mock.patch(
- 'nikola.plugins.command.init.CommandInit.copy_sample_site', self.copy_sample_site)
- create_configuration_patch = mock.patch(
- 'nikola.plugins.command.init.CommandInit.create_configuration', self.create_configuration)
- create_empty_site_patch = mock.patch(
- 'nikola.plugins.command.init.CommandInit.create_empty_site', self.create_empty_site)
-
- self.patches = [ask_questions_patch, copy_sample_site_patch,
- create_configuration_patch, create_empty_site_patch]
- for patch in self.patches:
- patch.start()
-
- self.init_command = nikola.plugins.command.init.CommandInit()
-
- def tearDown(self):
- for patch in self.patches:
- patch.stop()
- del self.patches
-
- del self.copy_sample_site
- del self.create_configuration
- del self.create_empty_site
-
- def test_init_default(self):
- self.init_command.execute()
-
- self.assertTrue(self.ask_questions.called)
- self.assertTrue(self.create_configuration.called)
- self.assertFalse(self.copy_sample_site.called)
- self.assertTrue(self.create_empty_site.called)
-
- def test_init_args(self):
- arguments = dict(options={'demo': True, 'quiet': True}, args=['destination'])
- self.init_command.execute(**arguments)
-
- self.assertFalse(self.ask_questions.called)
- self.assertTrue(self.create_configuration.called)
- self.assertTrue(self.copy_sample_site.called)
- self.assertFalse(self.create_empty_site.called)
-
- def test_init_called_without_target_quiet(self):
- self.init_command.execute(**dict(options={'quiet': True}))
-
- self.assertFalse(self.ask_questions.called)
- self.assertFalse(self.create_configuration.called)
- self.assertFalse(self.copy_sample_site.called)
- self.assertFalse(self.create_empty_site.called)
-
- def test_init_empty_dir(self):
- self.init_command.execute(args=['destination'])
-
- self.assertTrue(self.ask_questions.called)
- self.assertTrue(self.create_configuration.called)
- self.assertFalse(self.copy_sample_site.called)
- self.assertTrue(self.create_empty_site.called)
-
-
-class InitHelperTests(unittest.TestCase):
- """Test helper functions provided with the init command."""
-
- def test_configure_translations_without_additional_languages(self):
- """
- Testing the configuration of the translation when no additional language has been found.
- """
- translations_cfg = format_default_translations_config(set())
- self.assertEqual(SAMPLE_CONF["TRANSLATIONS"], translations_cfg)
-
- def test_configure_translations_with_2_additional_languages(self):
- """
- Testing the configuration of the translation when no additional language has been found.
- """
- translations_cfg = format_default_translations_config(
- set(["es", "en"]))
- self.assertEqual("""{
+from unittest import mock
+
+import pytest
+
+from nikola.plugins.command.init import (
+ SAMPLE_CONF,
+ CommandInit,
+ format_default_translations_config,
+)
+
+from .helper import cd
+
+
+def test_command_init_with_defaults(
+ init_command,
+ ask_questions,
+ copy_sample_site,
+ create_configuration,
+ create_empty_site,
+):
+ init_command.execute()
+
+ assert ask_questions.called
+ assert create_configuration.called
+ assert not copy_sample_site.called
+ assert create_empty_site.called
+
+
+def test_command_init_with_arguments(
+ init_command,
+ ask_questions,
+ copy_sample_site,
+ create_configuration,
+ create_empty_site,
+):
+ arguments = dict(options={"demo": True, "quiet": True}, args=["destination"])
+ init_command.execute(**arguments)
+
+ assert not ask_questions.called
+ assert create_configuration.called
+ assert copy_sample_site.called
+ assert not create_empty_site.called
+
+
+def test_init_called_without_target_quiet(
+ init_command,
+ ask_questions,
+ copy_sample_site,
+ create_configuration,
+ create_empty_site,
+):
+ init_command.execute(**{"options": {"quiet": True}})
+
+ assert not ask_questions.called
+ assert not create_configuration.called
+ assert not copy_sample_site.called
+ assert not create_empty_site.called
+
+
+def test_command_init_with_empty_dir(
+ init_command,
+ ask_questions,
+ copy_sample_site,
+ create_configuration,
+ create_empty_site,
+):
+ init_command.execute(args=["destination"])
+
+ assert ask_questions.called
+ assert create_configuration.called
+ assert not copy_sample_site.called
+ assert create_empty_site.called
+
+
+def test_configure_translations_without_additional_languages():
+ """
+ Testing the configuration of the translation when no additional language has been found.
+ """
+ translations_cfg = format_default_translations_config(set())
+ assert SAMPLE_CONF["TRANSLATIONS"] == translations_cfg
+
+
+def test_configure_translations_with_2_additional_languages():
+ """
+ Testing the configuration of the translation when two additional languages are given.
+ """
+ translations_cfg = format_default_translations_config(set(["es", "en"]))
+ assert translations_cfg == """{
DEFAULT_LANG: "",
"en": "./en",
"es": "./es",
-}""", translations_cfg)
+}"""
-if __name__ == '__main__':
- unittest.main()
+@pytest.fixture
+def init_command(
+ tmpdir, ask_questions, copy_sample_site, create_configuration, create_empty_site
+):
+ with mock.patch(
+ "nikola.plugins.command.init.CommandInit.ask_questions", ask_questions
+ ):
+ with mock.patch(
+ "nikola.plugins.command.init.CommandInit.copy_sample_site", copy_sample_site
+ ):
+ with mock.patch(
+ "nikola.plugins.command.init.CommandInit.create_configuration",
+ create_configuration,
+ ):
+ with mock.patch(
+ "nikola.plugins.command.init.CommandInit.create_empty_site",
+ create_empty_site,
+ ):
+ with cd(str(tmpdir)):
+ yield CommandInit()
+
+
+@pytest.fixture
+def ask_questions():
+ return mock.MagicMock()
+
+
+@pytest.fixture
+def copy_sample_site():
+ return mock.MagicMock()
+
+
+@pytest.fixture
+def create_configuration():
+ return mock.MagicMock()
+
+
+@pytest.fixture
+def create_empty_site():
+ return mock.MagicMock()
diff --git a/tests/test_commands.py b/tests/test_commands.py
deleted file mode 100644
index d70d164..0000000
--- a/tests/test_commands.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals, absolute_import
-
-import os
-import sys
-
-import unittest
-
-from nikola.plugins.command.version import CommandVersion
-
-
-class CommandVersionCallTest(unittest.TestCase):
- def test_version(self):
- """Test `nikola version`."""
- CommandVersion().execute()
diff --git a/tests/test_compile_markdown.py b/tests/test_compile_markdown.py
index 47cbdbb..88ac290 100644
--- a/tests/test_compile_markdown.py
+++ b/tests/test_compile_markdown.py
@@ -1,108 +1,78 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-import os
-import sys
-
-
import io
-import shutil
-import tempfile
-import unittest
from os import path
+import pytest
+
from nikola.plugins.compile.markdown import CompileMarkdown
-from .base import BaseTestCase, FakeSite
+from .helper import FakeSite
+
+
+@pytest.mark.parametrize(
+ "input_str, expected_output",
+ [
+ pytest.param("", "", id="empty"),
+ pytest.param(
+ "[podcast]https://archive.org/download/Rebeldes_Stereotipos/rs20120609_1.mp3[/podcast]",
+ '<p><audio controls=""><source src="https://archive.org/download/Rebeldes_Stereotipos/rs20120609_1.mp3" type="audio/mpeg"></source></audio></p>',
+ id="mdx podcast",
+ ),
+ pytest.param(
+ "~~striked out text~~",
+ "<p><del>striked out text</del></p>",
+ id="strikethrough",
+ ),
+ pytest.param(
+ """\
+ #!python
+ from this
+""",
+ """\
+<table class="codehilitetable"><tr><td class="linenos">\
+<div class="linenodiv"><pre>1</pre></div>\
+</td><td class="code"><pre class="code literal-block"><span></span>\
+<code><span class="kn">from</span> <span class="nn">this</span>
+</code></pre>
+</td></tr></table>
+""",
+ id="hilite",
+ ),
+ ],
+)
+def test_compiling_markdown(
+ compiler, input_path, output_path, input_str, expected_output
+):
+ output = markdown_compile(compiler, input_path, output_path, input_str)
+ assert output.strip() == expected_output.strip()
-class CompileMarkdownTests(BaseTestCase):
- def setUp(self):
- self.tmp_dir = tempfile.mkdtemp()
- self.input_path = path.join(self.tmp_dir, 'input.markdown')
- self.output_path = path.join(self.tmp_dir, 'output.html')
- self.compiler = CompileMarkdown()
- self.compiler.set_site(FakeSite())
+@pytest.fixture(scope="module")
+def site():
+ return FakeSite()
- def compile(self, input_string):
- with io.open(self.input_path, "w+", encoding="utf8") as input_file:
- input_file.write(input_string)
- self.compiler.compile_html(self.input_path, self.output_path)
+@pytest.fixture(scope="module")
+def compiler(site):
+ compiler = CompileMarkdown()
+ compiler.set_site(site)
+ return compiler
- output_str = None
- with io.open(self.output_path, "r", encoding="utf8") as output_path:
- output_str = output_path.read()
- return output_str
+@pytest.fixture
+def input_path(tmpdir):
+ return path.join(str(tmpdir), "input.markdown")
- def tearDown(self):
- shutil.rmtree(self.tmp_dir)
- def test_compile_html_empty(self):
- input_str = ''
- actual_output = self.compile(input_str)
- self.assertEquals(actual_output, '')
+@pytest.fixture
+def output_path(tmpdir):
+ return path.join(str(tmpdir), "output.html")
- def test_compile_html_code_hilite(self):
- input_str = '''\
- #!python
- from this
-'''
- expected_output = '''\
-<table class="codehilitetable"><tr><td class="linenos">\
-<div class="linenodiv"><pre>1</pre></div>\
-</td><td class="code"><pre class="code literal-block">\
-<span class="kn">from</span> <span class="nn">this</span>
-</pre>
-</td></tr></table>
-'''
-
- actual_output = self.compile(input_str)
- self.assertEquals(actual_output.strip(), expected_output.strip())
-
- def test_compile_html_gist(self):
- input_str = '''\
-Here's a gist file inline:
-[:gist: 4747847 zen.py]
-
-Cool, eh?
-'''
- expected_output = '''\
-<p>Here's a gist file inline:
-<div class="gist">
-<script src="https://gist.github.com/4747847.js?file=zen.py"></script>
-<noscript>
-<pre>import this</pre>
-</noscript>
-</div>
-</p>
-<p>Cool, eh?</p>
-'''
- actual_output = self.compile(input_str)
- self.assertEquals(actual_output.strip(), expected_output.strip())
-
- def test_compile_html_gist_2(self):
- input_str = '''\
-Here's a gist file inline, using reStructuredText syntax:
-..gist:: 4747847 zen.py
-
-Cool, eh?
-'''
- expected_output = '''\
-<p>Here's a gist file inline, using reStructuredText syntax:
-<div class="gist">
-<script src="https://gist.github.com/4747847.js?file=zen.py"></script>
-<noscript>
-<pre>import this</pre>
-</noscript>
-</div>
-</p>
-<p>Cool, eh?</p>
-'''
- actual_output = self.compile(input_str)
- self.assertEquals(actual_output.strip(), expected_output.strip())
-
-
-if __name__ == '__main__':
- unittest.main()
+
+def markdown_compile(compiler, input_path, output_path, text):
+ with io.open(input_path, "w+", encoding="utf8") as input_file:
+ input_file.write(text)
+
+ compiler.compile(input_path, output_path, lang="en")
+
+ with io.open(output_path, "r", encoding="utf8") as output_path:
+ return output_path.read()
diff --git a/tests/test_config.py b/tests/test_config.py
new file mode 100644
index 0000000..c67929d
--- /dev/null
+++ b/tests/test_config.py
@@ -0,0 +1,63 @@
+import os
+import re
+
+import pytest
+
+from nikola import __main__ as nikola
+
+
+def test_simple_config(simple_config, metadata_option):
+ """Check whether configuration-files without ineritance are interpreted correctly."""
+ assert simple_config[metadata_option]["ID"] == "conf"
+
+
+def test_inherited_config(simple_config, metadata_option, complex_config):
+ """Check whether configuration-files with ineritance are interpreted correctly."""
+ check_base_equality(simple_config, metadata_option, complex_config)
+ assert complex_config[metadata_option]["ID"] == "prod"
+
+
+def test_config_with_illegal_filename(
+ simple_config, metadata_option, complex_filename_config
+):
+ """Check whether files with illegal module-name characters can be set as config-files, too."""
+ check_base_equality(simple_config, metadata_option, complex_filename_config)
+ assert complex_filename_config[metadata_option]["ID"] == "illegal"
+
+
+@pytest.fixture(scope="module")
+def simple_config(data_dir):
+ nikola.main(["--conf=" + os.path.join(data_dir, "conf.py")])
+ return nikola.config
+
+
+@pytest.fixture(scope="module")
+def data_dir(test_dir):
+ return os.path.join(test_dir, "data", "test_config")
+
+
+@pytest.fixture
+def metadata_option():
+ return "ADDITIONAL_METADATA"
+
+
+@pytest.fixture(scope="module")
+def complex_config(data_dir):
+ nikola.main(["--conf=" + os.path.join(data_dir, "prod.py")])
+ return nikola.config
+
+
+@pytest.fixture(scope="module")
+def complex_filename_config(data_dir):
+ config_path = os.path.join(
+ data_dir, "config.with+illegal(module)name.characters.py"
+ )
+ nikola.main(["--conf=" + config_path])
+ return nikola.config
+
+
+def check_base_equality(base_config, metadata_option, config):
+ """Check whether the specified `config` equals the base config."""
+ for option in base_config.keys():
+ if re.match("^[A-Z]+(_[A-Z]+)*$", option) and option != metadata_option:
+ assert base_config[option] == config[option]
diff --git a/tests/test_integration.py b/tests/test_integration.py
deleted file mode 100644
index 4bfd1d2..0000000
--- a/tests/test_integration.py
+++ /dev/null
@@ -1,600 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals, print_function, absolute_import
-
-import os
-import sys
-
-import io
-import locale
-import shutil
-import subprocess
-import tempfile
-import unittest
-
-import lxml.html
-import pytest
-
-from nikola import __main__
-import nikola
-import nikola.plugins.command
-import nikola.plugins.command.init
-import nikola.utils
-
-from .base import BaseTestCase, cd, LocaleSupportInTesting
-
-LocaleSupportInTesting.initialize()
-
-
-class EmptyBuildTest(BaseTestCase):
- """Basic integration testcase."""
-
- dataname = None
-
- @classmethod
- def setUpClass(cls):
- """Setup a demo site."""
- # for tests that need bilingual support override language_settings
- cls.language_settings()
- cls.startdir = os.getcwd()
- cls.tmpdir = tempfile.mkdtemp()
- cls.target_dir = os.path.join(cls.tmpdir, "target")
- cls.init_command = nikola.plugins.command.init.CommandInit()
- cls.fill_site()
- cls.patch_site()
- cls.build()
-
- @classmethod
- def language_settings(cls):
- LocaleSupportInTesting.initialize_locales_for_testing("unilingual")
-
- @classmethod
- def fill_site(self):
- """Add any needed initial content."""
- self.init_command.create_empty_site(self.target_dir)
- self.init_command.create_configuration(self.target_dir)
-
- if self.dataname:
- src = os.path.join(os.path.dirname(__file__), 'data',
- self.dataname)
- for root, dirs, files in os.walk(src):
- for src_name in files:
- rel_dir = os.path.relpath(root, src)
- dst_file = os.path.join(self.target_dir, rel_dir, src_name)
- src_file = os.path.join(root, src_name)
- shutil.copy2(src_file, dst_file)
-
- @classmethod
- def patch_site(self):
- """Make any modifications you need to the site."""
-
- @classmethod
- def build(self):
- """Build the site."""
- with cd(self.target_dir):
- __main__.main(["build"])
-
- @classmethod
- def tearDownClass(self):
- """Remove the demo site."""
- # Don't saw off the branch you're sitting on!
- os.chdir(self.startdir)
- # ignore_errors=True for windows by issue #782
- shutil.rmtree(self.tmpdir, ignore_errors=(sys.platform == 'win32'))
- # Fixes Issue #438
- try:
- del sys.modules['conf']
- except KeyError:
- pass
- # clear LocaleBorg state
- nikola.utils.LocaleBorg.reset()
- if hasattr(self.__class__, "ol"):
- delattr(self.__class__, "ol")
-
- def test_build(self):
- """Ensure the build did something."""
- index_path = os.path.join(
- self.target_dir, "output", "archive.html")
- self.assertTrue(os.path.isfile(index_path))
-
-
-class DemoBuildTest(EmptyBuildTest):
- """Test that a default build of --demo works."""
-
- @classmethod
- def fill_site(self):
- """Fill the site with demo content."""
- self.init_command.copy_sample_site(self.target_dir)
- self.init_command.create_configuration(self.target_dir)
- src1 = os.path.join(os.path.dirname(__file__), 'data', '1-nolinks.rst')
- dst1 = os.path.join(self.target_dir, 'posts', '1.rst')
- shutil.copy(src1, dst1)
- # File for Issue #374 (empty post text)
- with io.open(os.path.join(self.target_dir, 'posts', 'empty.txt'), "w+", encoding="utf8") as outf:
- outf.write(
- ".. title: foobar\n"
- ".. slug: foobar\n"
- ".. date: 2013-03-06 19:08:15\n"
- )
-
- def test_index_in_sitemap(self):
- sitemap_path = os.path.join(self.target_dir, "output", "sitemap.xml")
- sitemap_data = io.open(sitemap_path, "r", encoding="utf8").read()
- self.assertTrue('<loc>https://example.com/index.html</loc>' in sitemap_data)
-
- def test_avoid_double_slash_in_rss(self):
- rss_path = os.path.join(self.target_dir, "output", "rss.xml")
- rss_data = io.open(rss_path, "r", encoding="utf8").read()
- self.assertFalse('https://example.com//' in rss_data)
-
-
-class RepeatedPostsSetting(DemoBuildTest):
- """Duplicate POSTS, should not read each post twice, which causes conflicts."""
- @classmethod
- def patch_site(self):
- """Set the SITE_URL to have a path"""
- conf_path = os.path.join(self.target_dir, "conf.py")
- with io.open(conf_path, "a", encoding="utf8") as outf:
- outf.write('\nPOSTS = (("posts/*.txt", "posts", "post.tmpl"),("posts/*.txt", "posts", "post.tmpl"))\n')
-
-
-class FuturePostTest(EmptyBuildTest):
- """Test a site with future posts."""
-
- @classmethod
- def fill_site(self):
- import datetime
- from nikola.utils import current_time
- self.init_command.copy_sample_site(self.target_dir)
- self.init_command.create_configuration(self.target_dir)
-
- # Change COMMENT_SYSTEM_ID to not wait for 5 seconds
- with io.open(os.path.join(self.target_dir, 'conf.py'), "a+", encoding="utf8") as outf:
- outf.write('\nCOMMENT_SYSTEM_ID = "nikolatest"\n')
-
- with io.open(os.path.join(self.target_dir, 'posts', 'empty1.txt'), "w+", encoding="utf8") as outf:
- outf.write(
- ".. title: foo\n"
- ".. slug: foo\n"
- ".. date: %s\n" % (current_time() + datetime.timedelta(-1)).strftime('%Y-%m-%d %H:%M:%S')
- )
-
- with io.open(os.path.join(self.target_dir, 'posts', 'empty2.txt'), "w+", encoding="utf8") as outf:
- outf.write(
- ".. title: bar\n"
- ".. slug: bar\n"
- ".. date: %s\n" % (current_time() + datetime.timedelta(1)).strftime('%Y-%m-%d %H:%M:%S')
- )
-
- def test_future_post(self):
- """ Ensure that the future post is not present in the index and sitemap."""
- index_path = os.path.join(self.target_dir, "output", "index.html")
- sitemap_path = os.path.join(self.target_dir, "output", "sitemap.xml")
- foo_path = os.path.join(self.target_dir, "output", "posts", "foo.html")
- bar_path = os.path.join(self.target_dir, "output", "posts", "bar.html")
- self.assertTrue(os.path.isfile(index_path))
- self.assertTrue(os.path.isfile(foo_path))
- self.assertTrue(os.path.isfile(bar_path))
- index_data = io.open(index_path, "r", encoding="utf8").read()
- sitemap_data = io.open(sitemap_path, "r", encoding="utf8").read()
- self.assertTrue('foo.html' in index_data)
- self.assertFalse('bar.html' in index_data)
- self.assertTrue('foo.html' in sitemap_data)
- self.assertFalse('bar.html' in sitemap_data)
-
- # Run deploy command to see if future post is deleted
- with cd(self.target_dir):
- __main__.main(["deploy"])
-
- self.assertTrue(os.path.isfile(index_path))
- self.assertTrue(os.path.isfile(foo_path))
- self.assertFalse(os.path.isfile(bar_path))
-
-
-class TranslatedBuildTest(EmptyBuildTest):
- """Test a site with translated content."""
-
- dataname = "translated_titles"
-
- @classmethod
- def language_settings(cls):
- LocaleSupportInTesting.initialize_locales_for_testing("bilingual")
- # the other language
- cls.ol = LocaleSupportInTesting.langlocales["other"][0]
-
- def test_translated_titles(self):
- """Check that translated title is picked up."""
- en_file = os.path.join(self.target_dir, "output", "stories", "1.html")
- pl_file = os.path.join(self.target_dir, "output", self.ol, "stories", "1.html")
- # Files should be created
- self.assertTrue(os.path.isfile(en_file))
- self.assertTrue(os.path.isfile(pl_file))
- # And now let's check the titles
- with io.open(en_file, 'r', encoding='utf8') as inf:
- doc = lxml.html.parse(inf)
- self.assertEqual(doc.find('//title').text, 'Foo | Demo Site')
- with io.open(pl_file, 'r', encoding='utf8') as inf:
- doc = lxml.html.parse(inf)
- self.assertEqual(doc.find('//title').text, 'Bar | Demo Site')
-
-
-class TranslationsPatternTest1(TranslatedBuildTest):
- """Check that the path.lang.ext TRANSLATIONS_PATTERN works too"""
-
- @classmethod
- def patch_site(self):
- """Set the TRANSLATIONS_PATTERN to the old v6 default"""
- os.rename(os.path.join(self.target_dir, "stories", "1.%s.txt" % self.ol),
- os.path.join(self.target_dir, "stories", "1.txt.%s" % self.ol)
- )
- conf_path = os.path.join(self.target_dir, "conf.py")
- with io.open(conf_path, "r", encoding="utf-8") as inf:
- data = inf.read()
- data = data.replace('TRANSLATIONS_PATTERN = "{path}.{lang}.{ext}"',
- 'TRANSLATIONS_PATTERN = "{path}.{ext}.{lang}"')
- with io.open(conf_path, "w+", encoding="utf8") as outf:
- outf.write(data)
-
-
-class MissingDefaultLanguageTest(TranslatedBuildTest):
- """Make sure posts only in secondary languages work."""
-
- @classmethod
- def fill_site(self):
- super(MissingDefaultLanguageTest, self).fill_site()
- os.unlink(os.path.join(self.target_dir, "stories", "1.txt"))
-
- def test_translated_titles(self):
- """Do not test titles as we just removed the translation"""
- pass
-
-
-class TranslationsPatternTest2(TranslatedBuildTest):
- """Check that the path_lang.ext TRANSLATIONS_PATTERN works too"""
-
- @classmethod
- def patch_site(self):
- """Set the TRANSLATIONS_PATTERN to the old v6 default"""
- conf_path = os.path.join(self.target_dir, "conf.py")
- os.rename(os.path.join(self.target_dir, "stories", "1.%s.txt" % self.ol),
- os.path.join(self.target_dir, "stories", "1.txt.%s" % self.ol)
- )
- with io.open(conf_path, "r", encoding="utf-8") as inf:
- data = inf.read()
- data = data.replace('TRANSLATIONS_PATTERN = "{path}.{lang}.{ext}"',
- 'TRANSLATIONS_PATTERN = "{path}.{ext}.{lang}"')
- with io.open(conf_path, "w+", encoding="utf8") as outf:
- outf.write(data)
-
-
-class RelativeLinkTest(DemoBuildTest):
- """Check that SITE_URL with a path doesn't break links."""
-
- @classmethod
- def patch_site(self):
- """Set the SITE_URL to have a path"""
- conf_path = os.path.join(self.target_dir, "conf.py")
- with io.open(conf_path, "r", encoding="utf-8") as inf:
- data = inf.read()
- data = data.replace('SITE_URL = "https://example.com/"',
- 'SITE_URL = "https://example.com/foo/bar/"')
- with io.open(conf_path, "w+", encoding="utf8") as outf:
- outf.write(data)
-
- def test_relative_links(self):
- """Check that the links in output/index.html are correct"""
- test_path = os.path.join(self.target_dir, "output", "index.html")
- flag = False
- with open(test_path, "rb") as inf:
- data = inf.read()
- for _, _, url, _ in lxml.html.iterlinks(data):
- # Just need to be sure this one is ok
- if url.endswith("css"):
- self.assertFalse(url.startswith(".."))
- flag = True
- # But I also need to be sure it is there!
- self.assertTrue(flag)
-
- def test_index_in_sitemap(self):
- """Test that the correct path is in sitemap, and not the wrong one."""
- sitemap_path = os.path.join(self.target_dir, "output", "sitemap.xml")
- sitemap_data = io.open(sitemap_path, "r", encoding="utf8").read()
- self.assertFalse('<loc>https://example.com/</loc>' in sitemap_data)
- self.assertTrue('<loc>https://example.com/foo/bar/index.html</loc>' in sitemap_data)
-
-
-class TestCheck(DemoBuildTest):
- """The demo build should pass 'nikola check'"""
-
- def test_check_links(self):
- with cd(self.target_dir):
- try:
- __main__.main(['check', '-l'])
- except SystemExit as e:
- self.assertEqual(e.code, 0)
-
- def test_check_files(self):
- with cd(self.target_dir):
- try:
- __main__.main(['check', '-f'])
- except SystemExit as e:
- self.assertEqual(e.code, 0)
-
-
-class TestCheckAbsoluteSubFolder(TestCheck):
- """Validate links in a site which is:
-
- * built in URL_TYPE="absolute"
- * deployable to a subfolder (BASE_URL="https://example.com/foo/")
- """
-
- @classmethod
- def patch_site(self):
- conf_path = os.path.join(self.target_dir, "conf.py")
- with io.open(conf_path, "r", encoding="utf-8") as inf:
- data = inf.read()
- data = data.replace('SITE_URL = "https://example.com/"',
- 'SITE_URL = "https://example.com/foo/"')
- data = data.replace("# URL_TYPE = 'rel_path'",
- "URL_TYPE = 'absolute'")
- with io.open(conf_path, "w+", encoding="utf8") as outf:
- outf.write(data)
- outf.flush()
-
- def test_index_in_sitemap(self):
- """Test that the correct path is in sitemap, and not the wrong one."""
- sitemap_path = os.path.join(self.target_dir, "output", "sitemap.xml")
- sitemap_data = io.open(sitemap_path, "r", encoding="utf8").read()
- self.assertTrue('<loc>https://example.com/foo/index.html</loc>' in sitemap_data)
-
-
-class TestCheckFullPathSubFolder(TestCheckAbsoluteSubFolder):
- """Validate links in a site which is:
-
- * built in URL_TYPE="full_path"
- * deployable to a subfolder (BASE_URL="https://example.com/foo/")
- """
-
- @classmethod
- def patch_site(self):
- conf_path = os.path.join(self.target_dir, "conf.py")
- with io.open(conf_path, "r", encoding="utf-8") as inf:
- data = inf.read()
- data = data.replace('SITE_URL = "https://example.com/"',
- 'SITE_URL = "https://example.com/foo/"')
- data = data.replace("# URL_TYPE = 'rel_path'",
- "URL_TYPE = 'full_path'")
- with io.open(conf_path, "w+", encoding="utf8") as outf:
- outf.write(data)
- outf.flush()
-
-
-class TestCheckFailure(DemoBuildTest):
- """The demo build should pass 'nikola check'"""
-
- def test_check_links_fail(self):
- with cd(self.target_dir):
- os.unlink(os.path.join("output", "archive.html"))
- try:
- __main__.main(['check', '-l'])
- except SystemExit as e:
- self.assertNotEqual(e.code, 0)
-
- def test_check_files_fail(self):
- with cd(self.target_dir):
- with io.open(os.path.join("output", "foobar"), "w+", encoding="utf8") as outf:
- outf.write("foo")
- try:
- __main__.main(['check', '-f'])
- except SystemExit as e:
- self.assertNotEqual(e.code, 0)
-
-
-class RelativeLinkTest2(DemoBuildTest):
- """Check that dropping stories to the root doesn't break links."""
-
- @classmethod
- def patch_site(self):
- """Set the SITE_URL to have a path"""
- conf_path = os.path.join(self.target_dir, "conf.py")
- with io.open(conf_path, "r", encoding="utf-8") as inf:
- data = inf.read()
- data = data.replace('("stories/*.txt", "stories", "story.tmpl"),',
- '("stories/*.txt", "", "story.tmpl"),')
- data = data.replace('("stories/*.rst", "stories", "story.tmpl"),',
- '("stories/*.rst", "", "story.tmpl"),')
- data = data.replace('# INDEX_PATH = ""',
- 'INDEX_PATH = "blog"')
- with io.open(conf_path, "w+", encoding="utf8") as outf:
- outf.write(data)
- outf.flush()
-
- def test_relative_links(self):
- """Check that the links in a story are correct"""
- test_path = os.path.join(self.target_dir, "output", "about-nikola.html")
- flag = False
- with open(test_path, "rb") as inf:
- data = inf.read()
- for _, _, url, _ in lxml.html.iterlinks(data):
- # Just need to be sure this one is ok
- if url.endswith("css"):
- self.assertFalse(url.startswith(".."))
- flag = True
- # But I also need to be sure it is there!
- self.assertTrue(flag)
-
- def test_index_in_sitemap(self):
- """Test that the correct path is in sitemap, and not the wrong one."""
- sitemap_path = os.path.join(self.target_dir, "output", "sitemap.xml")
- sitemap_data = io.open(sitemap_path, "r", encoding="utf8").read()
- self.assertFalse('<loc>https://example.com/</loc>' in sitemap_data)
- self.assertTrue('<loc>https://example.com/blog/index.html</loc>' in sitemap_data)
-
-
-class MonthlyArchiveTest(DemoBuildTest):
- """Check that the monthly archives build and are correct."""
-
- @classmethod
- def patch_site(self):
- """Set the SITE_URL to have a path"""
- conf_path = os.path.join(self.target_dir, "conf.py")
- with io.open(conf_path, "r", encoding="utf-8") as inf:
- data = inf.read()
- data = data.replace('# CREATE_MONTHLY_ARCHIVE = False',
- 'CREATE_MONTHLY_ARCHIVE = True')
- with io.open(conf_path, "w+", encoding="utf8") as outf:
- outf.write(data)
- outf.flush()
-
- def test_monthly_archive(self):
- """See that it builds"""
- self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, 'target', 'output', '2012', '03', 'index.html')))
-
-
-class DayArchiveTest(DemoBuildTest):
- """Check that per-day archives build and are correct."""
-
- @classmethod
- def patch_site(self):
- """Set the SITE_URL to have a path"""
- conf_path = os.path.join(self.target_dir, "conf.py")
- with io.open(conf_path, "r", encoding="utf-8") as inf:
- data = inf.read()
- data = data.replace('# CREATE_DAILY_ARCHIVE = False',
- 'CREATE_DAILY_ARCHIVE = True')
- with io.open(conf_path, "w+", encoding="utf8") as outf:
- outf.write(data)
- outf.flush()
-
- def test_day_archive(self):
- """See that it builds"""
- self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, 'target', 'output', '2012', '03', '30', 'index.html')))
-
-
-class FullArchiveTest(DemoBuildTest):
- """Check that full archives build and are correct."""
-
- @classmethod
- def patch_site(self):
- """Set the SITE_URL to have a path"""
- conf_path = os.path.join(self.target_dir, "conf.py")
- with io.open(conf_path, "r", encoding="utf-8") as inf:
- data = inf.read()
- data = data.replace('# CREATE_FULL_ARCHIVES = False',
- 'CREATE_FULL_ARCHIVES = True')
- with io.open(conf_path, "w+", encoding="utf8") as outf:
- outf.write(data)
- outf.flush()
-
- def test_full_archive(self):
- """See that it builds"""
- self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, 'target', 'output', 'archive.html')))
- self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, 'target', 'output', '2012', 'index.html')))
- self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, 'target', 'output', '2012', '03', 'index.html')))
- self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, 'target', 'output', '2012', '03', '30', 'index.html')))
-
-
-class SubdirRunningTest(DemoBuildTest):
- """Check that running nikola from subdir works."""
-
- def test_subdir_run(self):
- """Check whether build works from posts/"""
-
- with cd(os.path.join(self.target_dir, 'posts')):
- result = __main__.main(['build'])
- self.assertEquals(result, 0)
-
-
-class InvariantBuildTest(EmptyBuildTest):
- """Test that a default build of --demo works."""
-
- @classmethod
- def build(self):
- """Build the site."""
- try:
- self.oldlocale = locale.getlocale()
- locale.setlocale(locale.LC_ALL, ("en_US", "utf8"))
- except:
- pytest.skip('no en_US locale!')
- else:
- with cd(self.target_dir):
- __main__.main(["build", "--invariant"])
- finally:
- try:
- locale.setlocale(locale.LC_ALL, self.oldlocale)
- except:
- pass
-
- @classmethod
- def fill_site(self):
- """Fill the site with demo content."""
- self.init_command.copy_sample_site(self.target_dir)
- self.init_command.create_configuration(self.target_dir)
- src1 = os.path.join(os.path.dirname(__file__), 'data', '1-nolinks.rst')
- dst1 = os.path.join(self.target_dir, 'posts', '1.rst')
- shutil.copy(src1, dst1)
- os.system('rm "{0}/stories/creating-a-theme.rst" "{0}/stories/extending.txt" "{0}/stories/internals.txt" "{0}/stories/manual.rst" "{0}/stories/social_buttons.txt" "{0}/stories/theming.rst" "{0}/stories/upgrading-to-v6.txt"'.format(self.target_dir))
-
- def test_invariance(self):
- """Compare the output to the canonical output."""
- if sys.version_info[0:2] != (2, 7):
- pytest.skip('only python 2.7 is supported right now')
- good_path = os.path.join(os.path.dirname(__file__), 'data', 'baseline{0[0]}.{0[1]}'.format(sys.version_info))
- if not os.path.exists(good_path):
- pytest.skip('no baseline found')
- with cd(self.target_dir):
- try:
- diff = subprocess.check_output(['diff', '-ubwr', good_path, 'output'])
- self.assertEqual(diff.strip(), '')
- except subprocess.CalledProcessError as exc:
- print('Unexplained diff for the invariance test. (-canonical +built)')
- print(exc.output.decode('utf-8'))
- self.assertEqual(exc.returncode, 0, 'Unexplained diff for the invariance test.')
-
-
-class RedirectionsTest1(TestCheck):
- """Check REDIRECTIONS"""
-
- @classmethod
- def patch_site(self):
- """"""
- conf_path = os.path.join(self.target_dir, "conf.py")
- with io.open(conf_path, "a", encoding="utf8") as outf:
- outf.write("""\n\nREDIRECTIONS = [ ("posts/foo.html", "/foo/bar.html"), ]\n\n""")
-
- @classmethod
- def fill_site(self):
- target_path = os.path.join(self.target_dir, "files", "foo", "bar.html")
- nikola.utils.makedirs(os.path.join(self.target_dir, "files", "foo"))
- with io.open(target_path, "w+", encoding="utf8") as outf:
- outf.write("foo")
-
-class RedirectionsTest2(TestCheck):
- """Check external REDIRECTIONS"""
-
- @classmethod
- def patch_site(self):
- """"""
- conf_path = os.path.join(self.target_dir, "conf.py")
- with io.open(conf_path, "a", encoding="utf8") as outf:
- outf.write("""\n\nREDIRECTIONS = [ ("foo.html", "http://www.example.com/"), ]\n\n""")
-
-class RedirectionsTest3(TestCheck):
- """Check relative REDIRECTIONS"""
-
- @classmethod
- def patch_site(self):
- """"""
- conf_path = os.path.join(self.target_dir, "conf.py")
- with io.open(conf_path, "a", encoding="utf8") as outf:
- outf.write("""\n\nREDIRECTIONS = [ ("foo.html", "foo/bar.html"), ]\n\n""")
-
- @classmethod
- def fill_site(self):
- target_path = os.path.join(self.target_dir, "files", "foo", "bar.html")
- nikola.utils.makedirs(os.path.join(self.target_dir, "files", "foo"))
- with io.open(target_path, "w+", encoding="utf8") as outf:
- outf.write("foo")
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/tests/test_locale.py b/tests/test_locale.py
index d24fdfb..eaeb8e1 100644
--- a/tests/test_locale.py
+++ b/tests/test_locale.py
@@ -1,244 +1,217 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-import os
-import sys
-
-
-# needed if @unittest.expectedFailure is used
-try:
- import unittest2 as unittest
-except:
- import unittest
-
-import nikola.nikola
-import nikola.utils
-from .base import LocaleSupportInTesting
-
-LocaleSupportInTesting.initialize_locales_for_testing('bilingual')
-lang_11, loc_11 = LocaleSupportInTesting.langlocales['default']
-lang_22, loc_22 = LocaleSupportInTesting.langlocales['other']
-
-
-# these are candidates to hardcoded locales, using str() for py2x setlocale
-loc_C = str('C')
-loc_Cutf8 = str('C.utf8')
-
-if sys.platform != 'win32':
- nikola.nikola.workaround_empty_LC_ALL_posix()
-
-
-class TestHarcodedFallbacks(unittest.TestCase):
- def test_hardcoded_fallbacks_work(self):
- # keep in sync with nikola.valid_locale_fallback
- if sys.platform == 'win32':
- self.assertTrue(nikola.nikola.is_valid_locale(str('English')))
- self.assertTrue(nikola.nikola.is_valid_locale(str('C')))
- else:
- # the 1st is desired in Travis, not a problem if fails in user host
- self.assertTrue(nikola.nikola.is_valid_locale(str('en_US.utf8')))
- # this is supposed to be always valid, and we need an universal
- # fallback. Failure is not a problem in user host if he / she
- # sets a valid (in his host) locale_fallback.
- self.assertTrue(nikola.nikola.is_valid_locale(str('C')))
-
-
-class TestConfigLocale(unittest.TestCase):
-
- def test_implicit_fallback(self):
- locale_fallback = None
- sanitized_fallback = nikola.nikola.valid_locale_fallback(
- desired_locale=locale_fallback)
- self.assertTrue(nikola.nikola.is_valid_locale(sanitized_fallback))
-
- def test_explicit_good_fallback(self):
- locale_fallback = loc_22
- sanitized_fallback = nikola.nikola.valid_locale_fallback(
- desired_locale=locale_fallback)
- self.assertEquals(sanitized_fallback, locale_fallback)
-
- def test_explicit_bad_fallback(self):
- locale_fallback = str('xyz')
- sanitized_fallback = nikola.nikola.valid_locale_fallback(
- desired_locale=locale_fallback)
- self.assertTrue(nikola.nikola.is_valid_locale(sanitized_fallback))
-
- def test_explicit_good_default(self):
- locale_fallback, locale_default, LOCALES, translations = (
- loc_22,
- loc_11,
- {},
- {lang_11: ''},
- )
- fallback, default, locales = nikola.nikola.sanitized_locales(
- locale_fallback,
- locale_default,
- LOCALES,
- translations)
- self.assertEquals(fallback, locale_fallback)
- self.assertEquals(default, locale_default)
-
- def test_explicit_bad_default(self):
- locale_fallback, locale_default, LOCALES, translations = (
- loc_22,
- str('xyz'),
- {},
- {lang_11: ''},
- )
- fallback, default, locales = nikola.nikola.sanitized_locales(
- locale_fallback,
- locale_default,
- LOCALES,
- translations)
- self.assertEquals(fallback, locale_fallback)
- self.assertEquals(default, fallback)
-
- def test_extra_locales_deleted(self):
- locale_fallback, locale_default, LOCALES, translations = (
- loc_22,
- None,
- {'@z': loc_22},
- {lang_11: ''},
- )
- fallback, default, locales = nikola.nikola.sanitized_locales(
- locale_fallback,
- locale_default,
- LOCALES,
- translations)
- self.assertTrue('@z' not in locales)
-
- def test_explicit_good_locale_retained(self):
- locale_fallback, locale_default, LOCALES, translations = (
- loc_22,
- loc_22,
- {lang_11: loc_11},
- {lang_11: ''},
- )
- fallback, default, locales = nikola.nikola.sanitized_locales(
- locale_fallback,
- locale_default,
- LOCALES,
- translations)
- self.assertEquals(locales[lang_11], str(LOCALES[lang_11]))
-
- def test_explicit_bad_locale_replaced_with_fallback(self):
- locale_fallback, locale_default, LOCALES, translations = (
- loc_22,
- loc_11,
- {lang_11: str('xyz')},
- {lang_11: ''},
- )
- fallback, default, locales = nikola.nikola.sanitized_locales(
- locale_fallback,
- locale_default,
- LOCALES,
- translations)
- self.assertEquals(locales['en'], locale_fallback)
-
- def test_impicit_locale_when_default_locale_defined(self):
- locale_fallback, locale_default, LOCALES, translations = (
- loc_11,
- loc_22,
- {},
- {lang_11: ''},
- )
- fallback, default, locales = nikola.nikola.sanitized_locales(
- locale_fallback,
- locale_default,
- LOCALES,
- translations)
- self.assertEquals(locales['en'], locale_default)
-
- def test_impicit_locale_when_default_locale_is_not_defined(self):
- # legacy mode, compat v6.0.4 : guess locale from lang
- locale_fallback, locale_default, LOCALES, translations = (
- loc_22,
- None,
- {},
- {lang_11: ''},
- )
- fallback, default, locales = nikola.nikola.sanitized_locales(
- locale_fallback,
- locale_default,
- LOCALES,
- translations)
- if sys.platform == 'win32':
- guess_locale_for_lang = nikola.nikola.guess_locale_from_lang_windows
- else:
- guess_locale_for_lang = nikola.nikola.guess_locale_from_lang_posix
-
- self.assertEquals(locales[lang_11], guess_locale_for_lang(lang_11))
-
-
-class TestCalendarRelated(unittest.TestCase):
- def test_type_of_month_name(self):
- """validate assumption calendar month name is of type str
-
- Yes, both in windows and linuxTravis, py 26, 27, 33
- """
- import calendar
- if sys.version_info[0] == 3: # Python 3
- with calendar.different_locale(loc_11):
- s = calendar.month_name[1]
- else: # Python 2
- with calendar.TimeEncoding(loc_11):
- s = calendar.month_name[1]
- self.assertTrue(type(s) == str)
-
-
-class TestLocaleBorg(unittest.TestCase):
- def test_initial_lang(self):
- lang_11, loc_11 = LocaleSupportInTesting.langlocales['default']
- lang_22, loc_22 = LocaleSupportInTesting.langlocales['other']
-
- locales = {lang_11: loc_11, lang_22: loc_22}
- initial_lang = lang_22
- nikola.utils.LocaleBorg.initialize(locales, initial_lang)
- self.assertEquals(initial_lang, nikola.utils.LocaleBorg().current_lang)
-
- def test_remembers_last_lang(self):
- lang_11, loc_11 = LocaleSupportInTesting.langlocales['default']
- lang_22, loc_22 = LocaleSupportInTesting.langlocales['other']
-
- locales = {lang_11: loc_11, lang_22: loc_22}
- initial_lang = lang_22
- nikola.utils.LocaleBorg.initialize(locales, initial_lang)
-
- nikola.utils.LocaleBorg().set_locale(lang_11)
- self.assertTrue(nikola.utils.LocaleBorg().current_lang, lang_11)
-
- def test_services_ensure_initialization(self):
- nikola.utils.LocaleBorg.reset()
- self.assertRaises(Exception, nikola.utils.LocaleBorg)
-
- def test_services_reject_dumb_wrong_call(self):
- lang_11, loc_11 = LocaleSupportInTesting.langlocales['default']
- nikola.utils.LocaleBorg.reset()
- self.assertRaises(Exception, nikola.utils.LocaleBorg.set_locale, lang_11)
- self.assertRaises(Exception, getattr, nikola.utils.LocaleBorg, 'current_lang')
-
- def test_set_locale_raises_on_invalid_lang(self):
- lang_11, loc_11 = LocaleSupportInTesting.langlocales['default']
- lang_22, loc_22 = LocaleSupportInTesting.langlocales['other']
-
- locales = {lang_11: loc_11, lang_22: loc_22}
- initial_lang = lang_22
- nikola.utils.LocaleBorg.initialize(locales, initial_lang)
- self.assertRaises(KeyError, nikola.utils.LocaleBorg().set_locale, '@z')
-
-
-class TestTestPreconditions(unittest.TestCase):
- """If this fails the other test in this module are mostly nonsense, and
- probably same for tests of multilingual features.
-
- Failure probably means the OS support for the failing locale is not
- instaled or environmet variables NIKOLA_LOCALE_DEFAULT or
- NIKOLA_LOCALE_OTHER with bad values.
- """
- def test_langlocale_default_availability(self):
- msg = "META ERROR: The pair lang, locale : {0} {1} is invalid"
- self.assertTrue(nikola.nikola.is_valid_locale(loc_11), msg.format(lang_11, loc_11))
+import datetime
+import unittest.mock
- def test_langlocale_other_availability(self):
- msg = "META ERROR: The pair lang, locale : {0} {1} is invalid"
- self.assertTrue(nikola.nikola.is_valid_locale(loc_22), msg.format(lang_22, loc_22))
+import dateutil
+import pytest
+
+from nikola.nikola import LEGAL_VALUES
+from nikola.utils import (
+ LocaleBorg,
+ LocaleBorgUninitializedException,
+ TranslatableSetting,
+)
+
+TESLA_BIRTHDAY = datetime.date(1856, 7, 10)
+TESLA_BIRTHDAY_DT = datetime.datetime(1856, 7, 10, 12, 34, 56)
+DT_EN_US = "July 10, 1856 at 12:34:56 PM UTC"
+DT_PL = "10 lipca 1856 12:34:56 UTC"
+
+
+@pytest.mark.parametrize("initial_lang", [None, ""])
+def test_initilalize_failure(initial_lang):
+ with pytest.raises(ValueError):
+ LocaleBorg.initialize({}, initial_lang)
+
+ assert not LocaleBorg.initialized
+
+
+@pytest.mark.parametrize("initial_lang", ["en", "pl"])
+def test_initialize(initial_lang):
+ LocaleBorg.initialize({}, initial_lang)
+ assert LocaleBorg.initialized
+ assert LocaleBorg().current_lang == initial_lang
+
+
+def test_uninitialized_error():
+ with pytest.raises(LocaleBorgUninitializedException):
+ LocaleBorg()
+
+
+@pytest.mark.parametrize(
+ "locale, expected_current_lang",
+ [
+ ("pl", "pl"),
+ pytest.param(
+ "xx", "xx", id="fake language"
+ ), # used to ensure any locale can be supported
+ ],
+)
+def test_set_locale(base_config, locale, expected_current_lang):
+ LocaleBorg().set_locale(locale)
+ assert LocaleBorg.initialized
+ assert LocaleBorg().current_lang == expected_current_lang
+
+
+def test_set_locale_for_template():
+ LocaleBorg.initialize({}, "en")
+ assert LocaleBorg().set_locale("xz") == "" # empty string for template ease of use
+
+
+def test_format_date_webiso_basic(base_config):
+ with unittest.mock.patch("babel.dates.format_datetime") as m:
+ formatted_date = LocaleBorg().formatted_date("webiso", TESLA_BIRTHDAY_DT)
+ assert formatted_date == "1856-07-10T12:34:56"
+ m.assert_not_called()
+
+
+@pytest.mark.parametrize("lang", ["en", "pl"])
+def test_format_date_basic(base_config, lang):
+ LocaleBorg.initialize({}, lang)
+ formatted_date = LocaleBorg().formatted_date(
+ "yyyy-MM-dd HH:mm:ss", TESLA_BIRTHDAY_DT
+ )
+ assert formatted_date == "1856-07-10 12:34:56"
+
+
+def test_format_date_long(base_config):
+ assert LocaleBorg().formatted_date("long", TESLA_BIRTHDAY_DT) == DT_EN_US
+ assert LocaleBorg().formatted_date("long", TESLA_BIRTHDAY_DT, "en") == DT_EN_US
+ assert LocaleBorg().formatted_date("long", TESLA_BIRTHDAY_DT, "pl") == DT_PL
+ LocaleBorg().set_locale("pl")
+ assert LocaleBorg().formatted_date("long", TESLA_BIRTHDAY_DT) == DT_PL
+ assert LocaleBorg().formatted_date("long", TESLA_BIRTHDAY_DT, "en") == DT_EN_US
+
+
+def test_format_date_timezone(base_config):
+ tesla_150_birthday_dtz = datetime.datetime(
+ 2006, 7, 10, 12, 34, 56, tzinfo=dateutil.tz.gettz("America/New_York")
+ )
+ formatted_date = LocaleBorg().formatted_date("long", tesla_150_birthday_dtz)
+ assert formatted_date == "July 10, 2006 at 12:34:56 PM -0400"
+
+ nodst = datetime.datetime(
+ 2006, 1, 10, 12, 34, 56, tzinfo=dateutil.tz.gettz("America/New_York")
+ )
+ formatted_date = LocaleBorg().formatted_date("long", nodst)
+ assert formatted_date == "January 10, 2006 at 12:34:56 PM -0500"
+
+
+@pytest.mark.parametrize(
+ "english_variant, expected_date",
+ [
+ pytest.param("en_US", DT_EN_US, id="US"),
+ pytest.param("en_GB", "10 July 1856 at 12:34:56 UTC", id="GB"),
+ ],
+)
+def test_format_date_locale_variants(english_variant, expected_date):
+ LocaleBorg.initialize({"en": english_variant}, "en")
+ assert LocaleBorg().formatted_date("long", TESLA_BIRTHDAY_DT, "en") == expected_date
+
+
+@pytest.mark.parametrize(
+ "lang, expected_string", [("en", "en July"), ("pl", "lipca pl")]
+)
+def test_format_date_translatablesetting(base_config, lang, expected_string):
+ df = TranslatableSetting(
+ "DATE_FORMAT", {"en": "'en' MMMM", "pl": "MMMM 'pl'"}, {"en": "", "pl": ""}
+ )
+ assert LocaleBorg().formatted_date(df, TESLA_BIRTHDAY_DT, lang) == expected_string
+
+
+@pytest.mark.parametrize(
+ "lang, expected_string",
+ [
+ pytest.param(None, "Foo July Bar", id="default"),
+ pytest.param("pl", "Foo lipiec Bar", id="pl"),
+ ],
+)
+def test_format_date_in_string_month(base_config, lang, expected_string):
+ formatted_date = LocaleBorg().format_date_in_string(
+ "Foo {month} Bar", TESLA_BIRTHDAY, lang
+ )
+ assert formatted_date == expected_string
+
+
+@pytest.mark.parametrize(
+ "lang, expected_string",
+ [
+ pytest.param(None, "Foo July 1856 Bar", id="default"),
+ pytest.param("pl", "Foo lipiec 1856 Bar", id="pl"),
+ ],
+)
+def test_format_date_in_string_month_year(base_config, lang, expected_string):
+ formatted_date = LocaleBorg().format_date_in_string(
+ "Foo {month_year} Bar", TESLA_BIRTHDAY, lang
+ )
+ assert formatted_date == expected_string
+
+
+@pytest.mark.parametrize(
+ "lang, expected_string",
+ [
+ pytest.param(None, "Foo July 10, 1856 Bar", id="default"),
+ pytest.param("pl", "Foo 10 lipca 1856 Bar", id="pl"),
+ ],
+)
+def test_format_date_in_string_month_day_year(base_config, lang, expected_string):
+ formatted_date = LocaleBorg().format_date_in_string(
+ "Foo {month_day_year} Bar", TESLA_BIRTHDAY, lang
+ )
+ assert formatted_date == expected_string
+
+
+@pytest.mark.parametrize(
+ "lang, expected_string",
+ [
+ pytest.param(None, "Foo 10 July 1856 Bar", id="default"),
+ pytest.param("pl", "Foo 10 lipca 1856 Bar", id="pl"),
+ ],
+)
+def test_format_date_in_string_month_day_year_gb(lang, expected_string):
+ LocaleBorg.initialize({"en": "en_GB"}, "en")
+ formatted_date = LocaleBorg().format_date_in_string(
+ "Foo {month_day_year} Bar", TESLA_BIRTHDAY, lang
+ )
+ assert formatted_date == expected_string
+
+
+@pytest.mark.parametrize(
+ "message, expected_string",
+ [
+ ("Foo {month:'miesiąca' MMMM} Bar", "Foo miesiąca lipca Bar"),
+ ("Foo {month_year:MMMM yyyy} Bar", "Foo lipca 1856 Bar"),
+ ],
+)
+def test_format_date_in_string_customization(base_config, message, expected_string):
+ formatted_date = LocaleBorg().format_date_in_string(message, TESLA_BIRTHDAY, "pl")
+ assert formatted_date == expected_string
+
+
+@pytest.mark.parametrize(
+ "lang, expected_format",
+ [("sr", "10. јул 1856. 12:34:56 UTC"), ("sr_latin", "10. jul 1856. 12:34:56 UTC")],
+)
+def test_locale_base(lang, expected_format):
+ LocaleBorg.initialize(LEGAL_VALUES["LOCALES_BASE"], "en")
+ formatted_date = LocaleBorg().formatted_date("long", TESLA_BIRTHDAY_DT, lang)
+ assert formatted_date == expected_format
+
+
+@pytest.fixture(autouse=True)
+def localeborg_reset():
+ """
+ Reset the LocaleBorg before and after every test.
+ """
+ LocaleBorg.reset()
+ assert not LocaleBorg.initialized
+ try:
+ yield
+ finally:
+ LocaleBorg.reset()
+ assert not LocaleBorg.initialized
+
+
+@pytest.fixture
+def base_config():
+ """A base config of LocaleBorg."""
+ LocaleBorg.initialize({}, "en")
diff --git a/tests/test_metadata_extractors.py b/tests/test_metadata_extractors.py
new file mode 100644
index 0000000..58dd7b5
--- /dev/null
+++ b/tests/test_metadata_extractors.py
@@ -0,0 +1,225 @@
+"""Test metadata extractors."""
+
+import os
+from unittest import mock
+
+import pytest
+
+from nikola.metadata_extractors import (
+ MetaCondition,
+ check_conditions,
+ default_metadata_extractors_by,
+ load_defaults,
+)
+from nikola.plugins.compile.rest import CompileRest
+from nikola.plugins.compile.markdown import CompileMarkdown
+from nikola.plugins.compile.ipynb import CompileIPynb
+from nikola.plugins.compile.html import CompileHtml
+from nikola.post import get_meta
+
+from .helper import FakeSite
+
+
+@pytest.mark.parametrize(
+ "filecount, expected, unexpected",
+ [(1, "onefile", "twofile"), (2, "twofile", "onefile")],
+)
+@pytest.mark.parametrize(
+ "format_lc, format_friendly",
+ [("nikola", "Nikola"), ("toml", "TOML"), ("yaml", "YAML")],
+)
+def test_builtin_extractors_rest(
+ metadata_extractors_by,
+ testfiledir,
+ filecount,
+ expected,
+ unexpected,
+ format_lc,
+ format_friendly,
+):
+ is_two_files = filecount == 2
+
+ source_filename = "f-rest-{0}-{1}.rst".format(filecount, format_lc)
+ metadata_filename = "f-rest-{0}-{1}.meta".format(filecount, format_lc)
+ source_path = os.path.join(testfiledir, source_filename)
+ metadata_path = os.path.join(testfiledir, metadata_filename)
+ post = FakePost(source_path, metadata_path, {}, None, metadata_extractors_by)
+
+ assert os.path.exists(source_path)
+ if is_two_files:
+ assert os.path.exists(metadata_path)
+
+ meta, extractor = get_meta(post, None)
+
+ assert meta
+ assert extractor is metadata_extractors_by["name"][format_lc]
+
+ assert meta["title"] == "T: reST, {0}, {1}".format(filecount, format_friendly)
+ assert meta["slug"] == "s-rest-{0}-{1}".format(filecount, format_lc)
+ assert expected in meta["tags"]
+ assert unexpected not in meta["tags"]
+ assert "meta" in meta["tags"]
+ assert format_friendly in meta["tags"]
+ assert "reST" in meta["tags"]
+ assert meta["date"] == "2017-07-01 00:00:00 UTC"
+
+
+@pytest.mark.parametrize(
+ "filecount, expected, unexpected",
+ [(1, "onefile", "twofile"), (2, "twofile", "onefile")],
+)
+def test_nikola_meta_markdown(
+ metadata_extractors_by, testfiledir, filecount, expected, unexpected
+):
+ is_two_files = filecount == 2
+
+ source_filename = "f-markdown-{0}-nikola.md".format(filecount)
+ metadata_filename = "f-markdown-{0}-nikola.meta".format(filecount)
+ source_path = os.path.join(testfiledir, source_filename)
+ metadata_path = os.path.join(testfiledir, metadata_filename)
+ post = FakePost(source_path, metadata_path, {}, None, metadata_extractors_by)
+
+ assert os.path.exists(source_path)
+ if is_two_files:
+ assert os.path.exists(metadata_path)
+
+ meta, extractor = get_meta(post, None)
+ assert extractor is metadata_extractors_by["name"]["nikola"]
+
+ assert meta["title"] == "T: Markdown, {0}, Nikola".format(filecount)
+ assert meta["slug"] == "s-markdown-{0}-nikola".format(filecount)
+ assert expected in meta["tags"]
+ assert unexpected not in meta["tags"]
+ assert "meta" in meta["tags"]
+ assert "Nikola" in meta["tags"]
+ assert "Markdown" in meta["tags"]
+ assert meta["date"] == "2017-07-01 00:00:00 UTC"
+
+
+@pytest.mark.parametrize(
+ "compiler, fileextension, compiler_lc, name",
+ [
+ (CompileRest, "rst", "rest", "reST"),
+ (CompileMarkdown, "md", "markdown", "Markdown"),
+ (CompileIPynb, "ipynb", "ipynb", "Jupyter Notebook"),
+ (CompileHtml, "html", "html", "HTML"),
+ ],
+)
+def test_compiler_metadata(
+ metadata_extractors_by, testfiledir, compiler, fileextension, compiler_lc, name
+):
+ source_filename = "f-{0}-1-compiler.{1}".format(compiler_lc, fileextension)
+ metadata_filename = "f-{0}-1-compiler.meta".format(compiler_lc)
+ title = "T: {0}, 1, compiler".format(name)
+ slug = "s-{0}-1-compiler".format(compiler_lc)
+ source_path = os.path.join(testfiledir, source_filename)
+ metadata_path = os.path.join(testfiledir, metadata_filename)
+
+ config = {
+ "USE_REST_DOCINFO_METADATA": True,
+ "MARKDOWN_EXTENSIONS": ["markdown.extensions.meta"],
+ }
+ site = FakeSite()
+ site.config.update(config)
+ compiler_obj = compiler()
+ compiler_obj.set_site(site)
+
+ post = FakePost(
+ source_path, metadata_path, config, compiler_obj, metadata_extractors_by
+ )
+
+ class FakeBorg:
+ current_lang = "en"
+
+ def __call__(self):
+ return self
+
+ with mock.patch("nikola.plugins.compile." + compiler_lc + ".LocaleBorg", FakeBorg):
+ meta, extractor = get_meta(post, None)
+
+ assert meta["title"] == title
+ assert meta["slug"] == slug
+ assert "meta" in meta["tags"]
+ assert "onefile" in meta["tags"]
+ assert "compiler" in meta["tags"]
+ assert name in meta["tags"]
+ assert meta["date"] == "2017-07-01 00:00:00 UTC"
+
+
+def test_yaml_none_handling(metadata_extractors_by):
+ yaml_extractor = metadata_extractors_by["name"]["yaml"]
+ meta = yaml_extractor.extract_text("---\ntitle: foo\nslug: null")
+ assert meta["title"] == "foo"
+ assert meta["slug"] == ""
+
+
+@pytest.mark.parametrize(
+ "conditions",
+ [
+ [(MetaCondition.config_bool, "baz"), (MetaCondition.config_present, "quux")],
+ pytest.param(
+ [(MetaCondition.config_bool, "quux")], marks=pytest.mark.xfail(strict=True)
+ ),
+ pytest.param(
+ [(MetaCondition.config_present, "foobar")],
+ marks=pytest.mark.xfail(strict=True),
+ ),
+ [(MetaCondition.extension, "bar")],
+ pytest.param(
+ [(MetaCondition.extension, "baz")], marks=pytest.mark.xfail(strict=True)
+ ),
+ [(MetaCondition.compiler, "foo")],
+ pytest.param(
+ [(MetaCondition.compiler, "foobar")], marks=pytest.mark.xfail(strict=True)
+ ),
+ pytest.param(
+ [(MetaCondition.never, None), (MetaCondition.config_present, "bar")],
+ marks=pytest.mark.xfail(strict=True),
+ ),
+ ],
+)
+def test_check_conditions(conditions, dummy_post):
+ filename = "foo.bar"
+ config = {"baz": True, "quux": False}
+ assert check_conditions(dummy_post, filename, conditions, config, "")
+
+
+class FakePost:
+ def __init__(
+ self, source_path, metadata_path, config, compiler, metadata_extractors_by
+ ):
+ self.source_path = source_path
+ self.metadata_path = metadata_path
+ self.is_two_file = True
+ self.config = {"TRANSLATIONS": {"en": "./"}, "DEFAULT_LANG": "en"}
+ self.config.update(config)
+ self.default_lang = self.config["DEFAULT_LANG"]
+ self.metadata_extractors_by = metadata_extractors_by
+ if compiler:
+ self.compiler = compiler
+
+ def translated_source_path(self, _):
+ return self.source_path
+
+
+@pytest.fixture
+def metadata_extractors_by():
+ metadata_extractors = default_metadata_extractors_by()
+ load_defaults(None, metadata_extractors)
+ return metadata_extractors
+
+
+@pytest.fixture(scope="module")
+def testfiledir(test_dir):
+ return os.path.join(test_dir, "data", "metadata_extractors")
+
+
+@pytest.fixture(scope="module")
+def dummy_post():
+ class DummyCompiler:
+ name = "foo"
+
+ class DummyPost:
+ compiler = DummyCompiler()
+
+ return DummyPost()
diff --git a/tests/test_path_handlers.py b/tests/test_path_handlers.py
new file mode 100644
index 0000000..23b6bf1
--- /dev/null
+++ b/tests/test_path_handlers.py
@@ -0,0 +1,114 @@
+"""Test that CATEGORIES_INDEX_PATH and TAGS_INDEX_PATH return the correct values on Unix and Windows."""
+from unittest import mock
+
+from nikola import Nikola
+from nikola.plugins.misc.taxonomies_classifier import TaxonomiesClassifier
+from nikola.plugins.task.authors import ClassifyAuthors
+from nikola.plugins.task.categories import ClassifyCategories
+from nikola.plugins.task.tags import ClassifyTags
+from nikola.plugins.task.taxonomies import RenderTaxonomies
+
+import pytest
+
+
+@pytest.fixture(params=[ClassifyAuthors, ClassifyCategories, ClassifyTags], ids=["authors", "categories", "tags"])
+def taxonomy(request):
+ return request.param()
+
+
+@pytest.fixture(params=[
+ "base:", "base:blog", "base:path/with/trailing/slash/", "base:/path/with/leading/slash",
+ "index:tags.html", "index:blog/tags.html", "index:path/to/tags.html", "index:/path/with/leading/slash.html",
+])
+def path(request):
+ return request.param
+
+
+@pytest.fixture
+def fixture(taxonomy, path):
+ scheme, _, path = path.partition(':')
+ append_index = scheme == 'base'
+ if isinstance(taxonomy, ClassifyAuthors) and append_index:
+ site = Nikola(TRANSLATIONS={"en": ""}, AUTHOR_PATH=path)
+ elif isinstance(taxonomy, ClassifyAuthors) and not append_index:
+ pytest.skip("There is no AUTHORS_INDEX_PATH setting")
+ elif isinstance(taxonomy, ClassifyCategories) and append_index:
+ site = Nikola(TRANSLATIONS={"en": ""}, CATEGORY_PATH=path)
+ elif isinstance(taxonomy, ClassifyCategories) and not append_index:
+ site = Nikola(TRANSLATIONS={"en": ""}, CATEGORIES_INDEX_PATH=path)
+ elif isinstance(taxonomy, ClassifyTags) and append_index:
+ site = Nikola(TRANSLATIONS={"en": ""}, TAG_PATH=path)
+ elif isinstance(taxonomy, ClassifyTags) and not append_index:
+ site = Nikola(TRANSLATIONS={"en": ""}, TAGS_INDEX_PATH=path)
+ else:
+ raise TypeError("Unknown taxonomy %r" % type(taxonomy))
+
+ site._template_system = mock.MagicMock()
+ site._template_system.template_deps.return_value = []
+ site._template_system.name = "dummy"
+ site.hierarchy_per_classification = {taxonomy.classification_name: {"en": []}}
+ site.posts_per_classification = {taxonomy.classification_name: {"en": {}}}
+ site.taxonomy_plugins = {taxonomy.classification_name: taxonomy}
+
+ taxonomy.set_site(site)
+
+ classifier = TaxonomiesClassifier()
+ classifier.set_site(site)
+
+ expected = path.strip("/")
+ if append_index:
+ expected += "/"
+ if not expected.startswith("/"):
+ expected = "/" + expected
+
+ return site, classifier, taxonomy, append_index, expected
+
+
+def test_render_taxonomies_permalink(fixture):
+ # Arrange
+ site, _, taxonomy, _, expected = fixture
+ renderer = RenderTaxonomies()
+ renderer.set_site(site)
+
+ # Act
+ tasks = list(renderer._generate_classification_overview(taxonomy, "en"))
+
+ # Assert
+ action, args = tasks[0]["actions"][0]
+ context = args[2]
+ assert context["permalink"] == expected
+
+
+def test_taxonomy_index_path_helper(fixture):
+ # Arrange
+ site, _, taxonomy, _, expected = fixture
+
+ # Act
+ path = site.path(taxonomy.classification_name + "_index", "name", "en", is_link=True)
+
+ # Assert
+ assert path == expected
+
+
+def test_taxonomy_classifier_index_path(fixture):
+ # Arrange
+ site, classifier, taxonomy, append_index, expected = fixture
+ if append_index:
+ expected += "index.html"
+
+ # Act
+ path = classifier._taxonomy_index_path("name", "en", taxonomy)
+
+ # Assert
+ assert path == [x for x in expected.split('/') if x]
+
+
+def test_taxonomy_overview_path(fixture):
+ # Arrange
+ _, _, taxonomy, append_index, expected = fixture
+
+ # Act
+ result = taxonomy.get_overview_path("en")
+
+ # Assert
+ assert result == ([x for x in expected.split('/') if x], "always" if append_index else "never")
diff --git a/tests/test_plugin_importing.py b/tests/test_plugin_importing.py
deleted file mode 100644
index f41a1fd..0000000
--- a/tests/test_plugin_importing.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals, absolute_import
-
-import os
-import sys
-
-
-import unittest
-
-
-class ImportPluginsTest(unittest.TestCase):
- def test_importing_command_import_wordpress(self):
- import nikola.plugins.command.import_wordpress # NOQA
-
- def test_importing_compile_rest(self):
- import nikola.plugins.compile.rest # NOQA
-
- def test_importing_plugin_compile_markdown(self):
- import nikola.plugins.compile.markdown # NOQA
diff --git a/tests/test_plugins.py b/tests/test_plugins.py
new file mode 100644
index 0000000..6760ad5
--- /dev/null
+++ b/tests/test_plugins.py
@@ -0,0 +1,16 @@
+"""
+Simple plugin tests.
+
+More advanced tests should be in a separate module.
+"""
+
+
+def test_command_version():
+ """Test `nikola version`."""
+ from nikola.plugins.command.version import CommandVersion
+
+ CommandVersion().execute()
+
+
+def test_importing_plugin_task_galleries():
+ import nikola.plugins.task.galleries # NOQA
diff --git a/tests/test_rss_feeds.py b/tests/test_rss_feeds.py
index f67ed40..d976579 100644
--- a/tests/test_rss_feeds.py
+++ b/tests/test_rss_feeds.py
@@ -1,156 +1,180 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import unicode_literals, absolute_import
-
import os
-import sys
-
-
+import re
from collections import defaultdict
from io import StringIO
-import os
-import re
-import unittest
+from unittest import mock
import dateutil.tz
+import pytest
from lxml import etree
-import mock
-
-from .base import LocaleSupportInTesting
-import nikola
-fake_conf = defaultdict(str)
-fake_conf['TIMEZONE'] = 'UTC'
-fake_conf['__tzinfo__'] = dateutil.tz.tzutc()
-fake_conf['DEFAULT_LANG'] = 'en'
-fake_conf['TRANSLATIONS'] = {'en': ''}
-fake_conf['BASE_URL'] = 'http://some.blog/'
-fake_conf['BLOG_AUTHOR'] = nikola.nikola.utils.TranslatableSetting('BLOG_AUTHOR', 'Nikola Tesla', ['en'])
-fake_conf['TRANSLATIONS_PATTERN'] = '{path}.{lang}.{ext}'
-
-
-class FakeCompiler(object):
+from nikola.nikola import Nikola, Post
+from nikola.utils import LocaleBorg, TranslatableSetting
+
+
+def test_feed_is_valid(rss_feed_content, rss_schema):
+ """
+ A testcase to check if the generated feed is valid.
+
+ Validation can be tested with W3 FEED Validator that can be found
+ at http://feedvalidator.org
+ """
+ document = etree.parse(StringIO(rss_feed_content))
+
+ assert rss_schema.validate(document)
+
+
+@pytest.fixture
+def rss_schema(rss_schema_filename):
+ with open(rss_schema_filename, "r") as rss_schema_file:
+ xmlschema_doc = etree.parse(rss_schema_file)
+
+ return etree.XMLSchema(xmlschema_doc)
+
+
+@pytest.fixture
+def rss_schema_filename(test_dir):
+ return os.path.join(test_dir, "data", "rss-2_0.xsd")
+
+
+@pytest.mark.parametrize("element", ["guid", "link"])
+def test_feed_items_have_valid_URLs(rss_feed_content, blog_url, element):
+ """
+ The items in the feed need to have valid urls in link and guid.
+
+ As stated by W3 FEED Validator:
+ * "link must be a full and valid URL"
+ * "guid must be a full URL, unless isPermaLink attribute is false: /weblog/posts/the-minimal-server.html"
+ """
+ # This validation regex is taken from django.core.validators
+ url_validation_regex = re.compile(
+ r"^(?:http|ftp)s?://" # http:// or https://
+ # domain...
+ r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|"
+ r"localhost|" # localhost...
+ # ...or ipv4
+ r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|"
+ # ...or ipv6
+ r"\[?[A-F0-9]*:[A-F0-9:]+\]?)" r"(?::\d+)?" r"(?:/?|[/?]\S+)$", # optional port
+ re.IGNORECASE,
+ )
+
+ def is_valid_URL(url):
+ return url_validation_regex.match(url) is not None
+
+ et = etree.parse(StringIO(rss_feed_content))
+ channel = et.find("channel")
+ item = channel.find("item")
+ element = item.find(element)
+
+ assert is_valid_URL(element.text)
+ assert blog_url in element.text
+
+
+@pytest.fixture(autouse=True)
+def localeborg(default_locale):
+ """
+ LocaleBorg with default settings
+ """
+ LocaleBorg.reset()
+ LocaleBorg.initialize({}, default_locale)
+ try:
+ yield
+ finally:
+ LocaleBorg.reset()
+
+
+@pytest.fixture
+def rss_feed_content(blog_url, config, default_locale):
+ default_post = {
+ "title": "post title",
+ "slug": "awesome_article",
+ "date": "2012-10-01 22:41",
+ "author": None,
+ "tags": "tags",
+ "link": "link",
+ "description": "description",
+ "enclosure": "http://www.example.org/foo.mp3",
+ "enclosure_length": "5",
+ }
+ meta_mock = mock.Mock(return_value=(defaultdict(str, default_post), None))
+ with mock.patch("nikola.post.get_meta", meta_mock):
+ with \
+ mock.patch(
+ "nikola.nikola.utils.os.path.isdir", mock.Mock(return_value=True)), \
+ mock.patch(
+ "nikola.nikola.Post.text", mock.Mock(return_value="some long text")
+ ):
+ with mock.patch(
+ "nikola.post.os.path.isfile", mock.Mock(return_value=True)):
+ example_post = Post(
+ "source.file",
+ config,
+ "blog_folder",
+ True,
+ {"en": ""},
+ "post.tmpl",
+ FakeCompiler(),
+ )
+
+ filename = "testfeed.rss"
+ opener_mock = mock.mock_open()
+
+ with mock.patch("nikola.nikola.io.open", opener_mock, create=True):
+ Nikola().generic_rss_renderer(
+ default_locale,
+ "blog_title",
+ blog_url,
+ "blog_description",
+ [example_post, ],
+ filename,
+ True,
+ False,
+ )
+
+ opener_mock.assert_called_once_with(filename, "w+", encoding="utf-8")
+
+ # Python 3 / unicode strings workaround
+ # lxml will complain if the encoding is specified in the
+ # xml when running with unicode strings.
+ # We do not include this in our content.
+ file_content = [call[1][0] for call in opener_mock.mock_calls[2:-1]][0]
+ splitted_content = file_content.split("\n")
+ # encoding_declaration = splitted_content[0]
+ content_without_encoding_declaration = splitted_content[1:]
+ yield "\n".join(content_without_encoding_declaration)
+
+
+@pytest.fixture
+def config(blog_url, default_locale):
+ fake_conf = defaultdict(str)
+ fake_conf["TIMEZONE"] = "UTC"
+ fake_conf["__tzinfo__"] = dateutil.tz.tzutc()
+ fake_conf["DEFAULT_LANG"] = default_locale
+ fake_conf["TRANSLATIONS"] = {default_locale: ""}
+ fake_conf["BASE_URL"] = blog_url
+ fake_conf["BLOG_AUTHOR"] = TranslatableSetting(
+ "BLOG_AUTHOR", "Nikola Tesla", [default_locale]
+ )
+ fake_conf["TRANSLATIONS_PATTERN"] = "{path}.{lang}.{ext}"
+
+ return fake_conf
+
+
+@pytest.fixture
+def blog_url():
+ return "http://some.blog"
+
+
+class FakeCompiler:
demote_headers = False
- compile_html = None
- extension = lambda self: '.html'
- name = "fake"
+ compile = None
+
+ def extension(self):
+ return ".html"
def read_metadata(*args, **kwargs):
return {}
def register_extra_dependencies(self, post):
pass
-
-
-class RSSFeedTest(unittest.TestCase):
- def setUp(self):
- LocaleSupportInTesting.initialize_locales_for_testing('unilingual')
- self.blog_url = "http://some.blog"
-
- with mock.patch('nikola.post.get_meta',
- mock.Mock(return_value=(
- ({'title': 'post title',
- 'slug': 'awesome_article',
- 'date': '2012-10-01 22:41',
- 'author': None,
- 'tags': 'tags',
- 'link': 'link',
- 'description': 'description',
- 'enclosure': 'http://www.example.org/foo.mp3'}, True)
- ))):
- with mock.patch('nikola.nikola.utils.os.path.isdir',
- mock.Mock(return_value=True)):
- with mock.patch('nikola.nikola.Post.text',
- mock.Mock(return_value='some long text')):
-
- example_post = nikola.nikola.Post('source.file',
- fake_conf,
- 'blog_folder',
- True,
- {'en': ''},
- 'post.tmpl',
- FakeCompiler())
-
- opener_mock = mock.mock_open()
-
- with mock.patch('nikola.nikola.io.open', opener_mock, create=True):
- nikola.nikola.Nikola().generic_rss_renderer('en',
- "blog_title",
- self.blog_url,
- "blog_description",
- [example_post,
- ],
- 'testfeed.rss',
- True,
- False)
-
- opener_mock.assert_called_once_with(
- 'testfeed.rss', 'w+', encoding='utf-8')
-
- # Python 3 / unicode strings workaround
- # lxml will complain if the encoding is specified in the
- # xml when running with unicode strings.
- # We do not include this in our content.
- file_content = [
- call[1][0]
- for call in opener_mock.mock_calls[2:-1]][0]
- splitted_content = file_content.split('\n')
- self.encoding_declaration = splitted_content[0]
- content_without_encoding_declaration = splitted_content[1:]
- self.file_content = '\n'.join(
- content_without_encoding_declaration)
-
- def tearDown(self):
- pass
-
- def test_feed_items_have_valid_URLs(self):
- '''The items in the feed need to have valid urls in link and guid.'''
- # This validation regex is taken from django.core.validators
- url_validation_regex = re.compile(r'^(?:http|ftp)s?://' # http:// or https://
- r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain...
- r'localhost|' # localhost...
- r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4
- r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6
- r'(?::\d+)?' # optional port
- r'(?:/?|[/?]\S+)$', re.IGNORECASE)
-
- def is_valid_URL(url):
- return url_validation_regex.match(url) is not None
-
- et = etree.parse(StringIO(self.file_content))
- channel = et.find('channel')
- item = channel.find('item')
- guid = item.find('guid')
- link = item.find('link')
-
- # As stated by W3 FEED Validator: "link must be a full and valid URL"
- self.assertTrue(is_valid_URL(link.text),
- 'The following URL is not valid: %s' % link.text)
- self.assertTrue(self.blog_url in link.text)
-
- # "guid must be a full URL, unless isPermaLink attribute
- # is false: /weblog/posts/the-minimal-server.html "
- self.assertTrue(is_valid_URL(guid.text),
- 'The following URL is not valid: %s' %
- guid.text)
- self.assertTrue(self.blog_url in guid.text)
-
- def test_feed_is_valid(self):
- '''
- A testcase to check if the generated feed is valid.
-
- Validation can be tested with W3 FEED Validator that can be found
- at http://feedvalidator.org
- '''
- rss_schema_filename = os.path.join(os.path.dirname(__file__),
- 'rss-2_0.xsd')
- with open(rss_schema_filename, 'r') as rss_schema_file:
- xmlschema_doc = etree.parse(rss_schema_file)
-
- xmlschema = etree.XMLSchema(xmlschema_doc)
- document = etree.parse(StringIO(self.file_content))
-
- self.assertTrue(xmlschema.validate(document))
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/tests/test_rst_compiler.py b/tests/test_rst_compiler.py
index 5bbd617..803fd79 100644
--- a/tests/test_rst_compiler.py
+++ b/tests/test_rst_compiler.py
@@ -1,303 +1,215 @@
-# coding: utf8
-# Author: Rodrigo Bistolfi
-# Date: 03/2013
-
+"""
+Test cases for Nikola ReST extensions.
-""" Test cases for Nikola ReST extensions.
-A base class ReSTExtensionTestCase provides the tests basic behaivor.
-Subclasses must override the "sample" class attribute with the ReST markup.
-The sample will be rendered as HTML using publish_parts() by setUp().
+A sample will be rendered by CompileRest.
One method is provided for checking the resulting HTML:
- * assertHTMLContains(element, attributes=None, text=None)
-
-The HTML is parsed with lxml for checking against the data you provide. The
-method takes an element argument, a string representing the *name* of an HTML
-tag, like "script" or "iframe". We will try to find this tag in the document
-and perform the tests on it. You can pass a dictionary to the attributes kwarg
-representing the name and the value of the tag attributes. The text kwarg takes
-a string argument, which will be tested against the contents of the HTML
-element.
-One last caveat: you need to url unquote your urls if you are going to test
-attributes like "src" or "link", since the HTML rendered by docutils will be
-always unquoted.
+ * assert_html_contains(html, element, attributes=None, text=None)
"""
+from io import StringIO
-from __future__ import unicode_literals, absolute_import
-
-import os
-import sys
-
-
-import io
-try:
- from io import StringIO
-except ImportError:
- from StringIO import StringIO # NOQA
-import tempfile
-
-import docutils
-from lxml import html
import pytest
-import unittest
+from lxml import html as lxml_html
import nikola.plugins.compile.rest
-from nikola.plugins.compile.rest import gist
-from nikola.plugins.compile.rest import vimeo
import nikola.plugins.compile.rest.listing
-from nikola.plugins.compile.rest.doc import Plugin as DocPlugin
-from nikola.utils import _reload
-from .base import BaseTestCase, FakeSite
-
-
-class ReSTExtensionTestCase(BaseTestCase):
- """ Base class for testing ReST extensions """
-
- sample = 'foo'
- deps = None
-
- def setUp(self):
- self.compiler = nikola.plugins.compile.rest.CompileRest()
- self.compiler.set_site(FakeSite())
- return super(ReSTExtensionTestCase, self).setUp()
-
- def basic_test(self):
- """ Parse cls.sample into a HTML document tree """
- self.setHtmlFromRst(self.sample)
-
- def setHtmlFromRst(self, rst):
- """ Create html output from rst string """
- tmpdir = tempfile.mkdtemp()
- inf = os.path.join(tmpdir, 'inf')
- outf = os.path.join(tmpdir, 'outf')
- depf = os.path.join(tmpdir, 'outf.dep')
- with io.open(inf, 'w+', encoding='utf8') as f:
- f.write(rst)
- self.html = self.compiler.compile_html(inf, outf)
- with io.open(outf, 'r', encoding='utf8') as f:
- self.html = f.read()
- os.unlink(inf)
- os.unlink(outf)
- if os.path.isfile(depf):
- with io.open(depf, 'r', encoding='utf8') as f:
- self.assertEqual(self.deps, f.read())
- os.unlink(depf)
- else:
- self.assertEqual(self.deps, None)
- os.rmdir(tmpdir)
- self.html_doc = html.parse(StringIO(self.html))
-
- def assertHTMLContains(self, element, attributes=None, text=None):
- """ Test if HTML document includes an element with the given
- attributes and text content
-
- """
- try:
- tag = next(self.html_doc.iter(element))
- except StopIteration:
- raise Exception("<{0}> not in {1}".format(element, self.html))
- else:
- if attributes:
- arg_attrs = set(attributes.items())
- tag_attrs = set(tag.items())
- self.assertTrue(arg_attrs.issubset(tag_attrs))
- if text:
- self.assertIn(text, tag.text)
-
-
-class ReSTExtensionTestCaseTestCase(ReSTExtensionTestCase):
- """ Simple test for our base class :) """
-
- sample = '.. raw:: html\n\n <iframe src="foo" height="bar">spam</iframe>'
-
- def test_test(self):
- self.basic_test()
- self.assertHTMLContains("iframe", attributes={"src": "foo"},
- text="spam")
- self.assertRaises(Exception, self.assertHTMLContains, "eggs", {})
-
-
-class MathTestCase(ReSTExtensionTestCase):
- sample = ':math:`e^{ix} = \cos x + i\sin x`'
-
- def test_math(self):
- """ Test that math is outputting TeX code."""
- self.basic_test()
- self.assertHTMLContains("span", attributes={"class": "math"},
- text="\(e^{ix} = \cos x + i\sin x\)")
+from nikola.plugins.compile.rest import vimeo
+from nikola.utils import _reload, LocaleBorg
+from .helper import FakeSite
-class GistTestCase(ReSTExtensionTestCase):
- """ Test GitHubGist.
- We will replace get_raw_gist() and get_raw_gist_with_filename()
- monkeypatching the GitHubGist class for avoiding network dependency
+def test_ReST_extension():
+ sample = '.. raw:: html\n\n <iframe src="foo" height="bar">spam</iframe>'
+ html = get_html_from_rst(sample)
+
+ assert_html_contains(html, "iframe", attributes={"src": "foo"}, text="spam")
+
+ with pytest.raises(Exception):
+ assert_html_contains("eggs", {})
+
+
+def test_math_extension_outputs_tex():
+ """Test that math is outputting TeX code."""
+ sample = r":math:`e^{ix} = \cos x + i\sin x`"
+ html = get_html_from_rst(sample)
+
+ assert_html_contains(
+ html,
+ "span",
+ attributes={"class": "math"},
+ text=r"\(e^{ix} = \cos x + i\sin x\)",
+ )
+
+
+def test_soundcloud_iframe():
+ """Test SoundCloud iframe tag generation"""
+
+ sample = ".. soundcloud:: SID\n :height: 400\n :width: 600"
+ html = get_html_from_rst(sample)
+ assert_html_contains(
+ html,
+ "iframe",
+ attributes={
+ "src": (
+ "https://w.soundcloud.com/player/"
+ "?url=http://api.soundcloud.com/"
+ "tracks/SID"
+ ),
+ "height": "400",
+ "width": "600",
+ },
+ )
+
+
+def test_youtube_iframe():
+ """Test Youtube iframe tag generation"""
+
+ sample = ".. youtube:: YID\n :height: 400\n :width: 600"
+ html = get_html_from_rst(sample)
+ assert_html_contains(
+ html,
+ "iframe",
+ attributes={
+ "src": (
+ "https://www.youtube-nocookie.com"
+ "/embed/YID?rel=0&"
+ "wmode=transparent"
+ ),
+ "height": "400",
+ "width": "600",
+ "frameborder": "0",
+ "allowfullscreen": "",
+ "allow": "encrypted-media",
+ },
+ )
+
+
+def test_vimeo(disable_vimeo_api_query):
+ """Test Vimeo iframe tag generation"""
+
+ sample = ".. vimeo:: VID\n :height: 400\n :width: 600"
+ html = get_html_from_rst(sample)
+ assert_html_contains(
+ html,
+ "iframe",
+ attributes={
+ "src": ("https://player.vimeo.com/" "video/VID"),
+ "height": "400",
+ "width": "600",
+ },
+ )
+
+
+@pytest.mark.parametrize(
+ "sample",
+ [
+ ".. code-block:: python\n\n import antigravity",
+ ".. sourcecode:: python\n\n import antigravity",
+ ],
+)
+def test_rendering_codeblock_alias(sample):
+ """Test CodeBlock aliases"""
+ get_html_from_rst(sample)
+
+
+def test_doc_doesnt_exist():
+ with pytest.raises(Exception):
+ assert_html_contains("anything", {})
+
+
+def test_doc():
+ sample = "Sample for testing my :doc:`fake-post`"
+ html = get_html_from_rst(sample)
+ assert_html_contains(
+ html, "a", text="Fake post", attributes={"href": "/posts/fake-post"}
+ )
+
+
+def test_doc_titled():
+ sample = "Sample for testing my :doc:`titled post <fake-post>`"
+ html = get_html_from_rst(sample)
+ assert_html_contains(
+ html, "a", text="titled post", attributes={"href": "/posts/fake-post"}
+ )
+
+
+@pytest.fixture(autouse=True, scope="module")
+def localeborg_base():
+ """A base config of LocaleBorg."""
+ LocaleBorg.reset()
+ assert not LocaleBorg.initialized
+ LocaleBorg.initialize({}, "en")
+ assert LocaleBorg.initialized
+ assert LocaleBorg().current_lang == "en"
+ try:
+ yield
+ finally:
+ LocaleBorg.reset()
+ assert not LocaleBorg.initialized
+
+
+def get_html_from_rst(rst):
+ """Create html output from rst string"""
+
+ compiler = nikola.plugins.compile.rest.CompileRest()
+ compiler.set_site(FakeSite())
+ return compiler.compile_string(rst)[0]
+
+
+class FakePost:
+ def __init__(self, outfile):
+ self._depfile = {outfile: []}
+
+
+def assert_html_contains(html, element, attributes=None, text=None):
"""
- gist_type = gist.GitHubGist
- sample = '.. gist:: fake_id\n :file: spam.py'
- sample_without_filename = '.. gist:: fake_id2'
-
- def setUp(self):
- """ Patch GitHubGist for avoiding network dependency """
- super(GistTestCase, self).setUp()
- self.gist_type.get_raw_gist_with_filename = lambda *_: 'raw_gist_file'
- self.gist_type.get_raw_gist = lambda *_: "raw_gist"
- _reload(nikola.plugins.compile.rest)
-
- @pytest.mark.skipif(True, reason="This test indefinitely skipped.")
- def test_gist(self):
- """ Test the gist directive with filename """
- self.setHtmlFromRst(self.sample)
- output = 'https://gist.github.com/fake_id.js?file=spam.py'
- self.assertHTMLContains("script", attributes={"src": output})
- self.assertHTMLContains("pre", text="raw_gist_file")
-
- @pytest.mark.skipif(True, reason="This test indefinitely skipped.")
- def test_gist_without_filename(self):
- """ Test the gist directive without filename """
- self.setHtmlFromRst(self.sample_without_filename)
- output = 'https://gist.github.com/fake_id2.js'
- self.assertHTMLContains("script", attributes={"src": output})
- self.assertHTMLContains("pre", text="raw_gist")
-
-
-class GistIntegrationTestCase(ReSTExtensionTestCase):
- """ Test requests integration. The gist plugin uses requests to fetch gist
- contents and place it in a noscript tag.
-
+ Test if HTML document includes an element with the given attributes
+ and text content.
+
+ The HTML is parsed with lxml for checking against the data you
+ provide. The method takes an element argument, a string representing
+ the *name* of an HTML tag, like "script" or "iframe".
+ We will try to find this tag in the document and perform the tests
+ on it. You can pass a dictionary to the attributes kwarg
+ representing the name and the value of the tag attributes. The text
+ kwarg takes a string argument, which will be tested against the
+ contents of the HTML element.
+
+ One last caveat: you need to url unquote your urls if you are going
+ to test attributes like "src" or "link", since the HTML rendered by
+ docutils will be always unquoted.
"""
- sample = '.. gist:: 1812835'
-
- def test_gist_integration(self):
- """ Fetch contents of the gist from GH and render in a noscript tag """
- self.basic_test()
- text = ('Be alone, that is the secret of invention: be alone, that is'
- ' when ideas are born. -- Nikola Tesla')
- self.assertHTMLContains('pre', text=text)
-
+ html_doc = lxml_html.parse(StringIO(html))
-class SlidesTestCase(ReSTExtensionTestCase):
- """ Slides test case """
+ try:
+ tag = next(html_doc.iter(element))
+ except StopIteration:
+ raise Exception("<{0}> not in {1}".format(element, html))
- sample = '.. slides:: IMG.jpg\n'
+ if attributes:
+ arg_attrs = set(attributes.items())
+ tag_attrs = set(tag.items())
+ assert arg_attrs.issubset(tag_attrs)
- def test_slides(self):
- """ Test the slides js generation and img tag creation """
- self.basic_test()
- self.assertHTMLContains("img", attributes={"src": "IMG.jpg"})
+ if text:
+ assert text in tag.text
-class SoundCloudTestCase(ReSTExtensionTestCase):
- """ SoundCloud test case """
-
- sample = '.. soundcloud:: SID\n :height: 400\n :width: 600'
-
- def test_soundcloud(self):
- """ Test SoundCloud iframe tag generation """
- self.basic_test()
- self.assertHTMLContains("iframe",
- attributes={"src": ("https://w.soundcloud.com"
- "/player/?url=http://"
- "api.soundcloud.com/"
- "tracks/SID"),
- "height": "400", "width": "600"})
-
+@pytest.fixture
+def disable_vimeo_api_query():
+ """
+ Disable query of the vimeo api over the wire
-class VimeoTestCase(ReSTExtensionTestCase):
- """Vimeo test.
Set Vimeo.request_size to False for avoiding querying the Vimeo api
- over the network
-
+ over the network.
"""
- sample = '.. vimeo:: VID\n :height: 400\n :width: 600'
-
- def setUp(self):
- """ Disable query of the vimeo api over the wire """
- vimeo.Vimeo.request_size = False
- super(VimeoTestCase, self).setUp()
+ before = vimeo.Vimeo.request_size
+ vimeo.Vimeo.request_size = False
+ try:
_reload(nikola.plugins.compile.rest)
-
- def test_vimeo(self):
- """ Test Vimeo iframe tag generation """
- self.basic_test()
- self.assertHTMLContains("iframe",
- attributes={"src": ("//player.vimeo.com/"
- "video/VID"),
- "height": "400", "width": "600"})
-
-
-class YoutubeTestCase(ReSTExtensionTestCase):
- """ Youtube test case """
-
- sample = '.. youtube:: YID\n :height: 400\n :width: 600'
-
- def test_youtube(self):
- """ Test Youtube iframe tag generation """
- self.basic_test()
- self.assertHTMLContains("iframe",
- attributes={"src": ("//www.youtube.com/"
- "embed/YID?rel=0&hd=1&"
- "wmode=transparent"),
- "height": "400", "width": "600"})
-
-
-class ListingTestCase(ReSTExtensionTestCase):
- """ Listing test case and CodeBlock alias tests """
-
- deps = None
- sample1 = '.. listing:: nikola.py python\n\n'
- sample2 = '.. code-block:: python\n\n import antigravity'
- sample3 = '.. sourcecode:: python\n\n import antigravity'
-
- # def test_listing(self):
- # """ Test that we can render a file object contents without errors """
- # with cd(os.path.dirname(__file__)):
- # self.deps = 'listings/nikola.py'
- # self.setHtmlFromRst(self.sample1)
-
- def test_codeblock_alias(self):
- """ Test CodeBlock aliases """
- self.deps = None
- self.setHtmlFromRst(self.sample2)
- self.setHtmlFromRst(self.sample3)
-
-
-class DocTestCase(ReSTExtensionTestCase):
- """ Ref role test case """
-
- sample = 'Sample for testing my :doc:`doesnt-exist-post`'
- sample1 = 'Sample for testing my :doc:`fake-post`'
- sample2 = 'Sample for testing my :doc:`titled post <fake-post>`'
-
- def setUp(self):
- # Initialize plugin, register role
- self.plugin = DocPlugin()
- self.plugin.set_site(FakeSite())
- # Hack to fix leaked state from integration tests
- try:
- f = docutils.parsers.rst.roles.role('doc', None, None, None)[0]
- f.site = FakeSite()
- except AttributeError:
- pass
- return super(DocTestCase, self).setUp()
-
- def test_doc_doesnt_exist(self):
- self.assertRaises(Exception, self.assertHTMLContains, 'anything', {})
-
- def test_doc(self):
- self.setHtmlFromRst(self.sample1)
- self.assertHTMLContains('a',
- text='Fake post',
- attributes={'href': '/posts/fake-post'})
-
- def test_doc_titled(self):
- self.setHtmlFromRst(self.sample2)
- self.assertHTMLContains('a',
- text='titled post',
- attributes={'href': '/posts/fake-post'})
-
-
-if __name__ == "__main__":
- unittest.main()
+ yield
+ finally:
+ vimeo.Vimeo.request_size = before
diff --git a/tests/test_scheduling.py b/tests/test_scheduling.py
index fddb4ff..af89afa 100644
--- a/tests/test_scheduling.py
+++ b/tests/test_scheduling.py
@@ -1,132 +1,137 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals, absolute_import
+"""
+Scheduling tests.
+
+These tests rely on a fixed time to work.
+In order to achieve this the fixture for `now` sets the expected time.
+"""
import datetime
-import locale
-import os
-import sys
import dateutil.parser
import dateutil.tz
import pytest
-from .base import BaseTestCase
-
-try:
- from freezegun import freeze_time
- _freeze_time = True
-except ImportError:
- _freeze_time = False
- freeze_time = lambda x: lambda y: y
-
-_NOW = datetime.datetime( # Thursday
- 2013, 8, 22, 10, 0, 0, tzinfo=dateutil.tz.tzutc())
-
-
-@pytest.mark.skipif(not _freeze_time, reason="freezegun not installed.")
-class TestScheduling(BaseTestCase):
-
- @classmethod
- def setUp(cls):
- d = [name for name in sys.modules if name.startswith("six.moves.")]
- cls.deleted = {}
- for name in d:
- cls.deleted[name] = sys.modules[name]
- del sys.modules[name]
-
- @classmethod
- def tearDown(cls):
- for name, mod in cls.deleted.items():
- sys.modules[name] = mod
-
- @freeze_time(_NOW)
- def test_get_date(self):
- from nikola.plugins.command.new_post import get_date
-
- FMT = '%Y-%m-%d %H:%M:%S %Z'
- NOW = _NOW.strftime(FMT)
- TODAY = dateutil.parser.parse(NOW)
- RULE_TH = 'RRULE:FREQ=WEEKLY;BYDAY=TH'
- RULE_FR = 'RRULE:FREQ=WEEKLY;BYDAY=FR'
- UTC = dateutil.tz.tzutc()
-
- # NOW does not match rule #########################################
- # No last date
- expected = TODAY.replace(day=23).strftime(FMT)
- self.assertEqual(expected, get_date(True, RULE_FR, tz=UTC))
- self.assertEqual(expected, get_date(True, RULE_FR, tz=UTC))
-
- # Last date in the past; doesn't match rule
- date = TODAY.replace(hour=7)
- expected = TODAY.replace(day=23, hour=7).strftime(FMT)
- self.assertEqual(expected, get_date(True, RULE_FR, date, tz=UTC))
-
- # Last date in the future; doesn't match rule
- date = TODAY.replace(day=24, hour=7)
- expected = TODAY.replace(day=30, hour=7).strftime(FMT)
- self.assertEqual(expected, get_date(True, RULE_FR, date, tz=UTC))
-
- # Last date in the past; matches rule
- date = TODAY.replace(day=16, hour=8)
- expected = TODAY.replace(day=23, hour=8).strftime(FMT)
- self.assertEqual(expected, get_date(True, RULE_FR, date, tz=UTC))
-
- # Last date in the future; matches rule
- date = TODAY.replace(day=23, hour=18)
- expected = TODAY.replace(day=30, hour=18).strftime(FMT)
- self.assertEqual(expected, get_date(True, RULE_FR, date, tz=UTC))
-
- # NOW matches rule ################################################
- # Not scheduling should return NOW
- self.assertEqual(NOW, get_date(False, RULE_TH, tz=UTC))
- # No last date
- self.assertEqual(NOW, get_date(True, RULE_TH, tz=UTC))
- self.assertEqual(NOW, get_date(True, RULE_TH, tz=UTC))
-
- # Last date in the past; doesn't match rule
- # Corresponding time has already passed, today
- date = TODAY.replace(day=21, hour=7)
- expected = TODAY.replace(day=29, hour=7).strftime(FMT)
- self.assertEqual(expected, get_date(True, RULE_TH, date, tz=UTC))
- # Corresponding time has not passed today
- date = TODAY.replace(day=21, hour=18)
- expected = TODAY.replace(day=22, hour=18).strftime(FMT)
- self.assertEqual(expected, get_date(True, RULE_TH, date, tz=UTC))
-
- # Last date in the future; doesn't match rule
- # Corresponding time has already passed, today
- date = TODAY.replace(day=24, hour=7)
- expected = TODAY.replace(day=29, hour=7).strftime(FMT)
- self.assertEqual(expected, get_date(True, RULE_TH, date, tz=UTC))
- # Corresponding time has not passed today
- date = TODAY.replace(day=24, hour=18)
- expected = TODAY.replace(day=29, hour=18).strftime(FMT)
- self.assertEqual(expected, get_date(True, RULE_TH, date, tz=UTC))
-
- # Last date in the past; matches rule
- # Corresponding time has already passed, today
- date = TODAY.replace(day=15, hour=7)
- expected = TODAY.replace(day=29, hour=7).strftime(FMT)
- self.assertEqual(expected, get_date(True, RULE_TH, date, tz=UTC))
- # Corresponding time has already passed, today; rule specifies HOUR
- date = TODAY.replace(day=15, hour=7)
- expected = TODAY.replace(day=29, hour=9).strftime(FMT)
- self.assertEqual(expected, get_date(True, RULE_TH + ';BYHOUR=9', date, tz=UTC))
- # Corresponding time has not passed today
- date = TODAY.replace(day=15, hour=18)
- expected = TODAY.replace(day=22, hour=18).strftime(FMT)
- self.assertEqual(expected, get_date(True, RULE_TH, date, tz=UTC))
-
- # Last date in the future; matches rule
- # Corresponding time has already passed, today
- date = TODAY.replace(day=29, hour=7)
- expected = TODAY.replace(day=5, month=9, hour=7).strftime(FMT)
- self.assertEqual(expected, get_date(True, RULE_TH, date, tz=UTC))
- # Corresponding time has not passed today
- date = TODAY.replace(day=22, hour=18)
- expected = TODAY.replace(day=29, hour=18).strftime(FMT)
- self.assertEqual(expected, get_date(True, RULE_TH, date, tz=UTC))
-
-if __name__ == '__main__':
- import unittest
- unittest.main()
+from nikola.plugins.command.new_post import get_date
+
+freezegun = pytest.importorskip("freezegun")
+freeze_time = freezegun.freeze_time
+
+UTC = dateutil.tz.tzutc()
+RULE_THURSDAYS = "RRULE:FREQ=WEEKLY;BYDAY=TH"
+RULE_FRIDAYS = "RRULE:FREQ=WEEKLY;BYDAY=FR"
+
+
+def test_current_time_not_matching_rule(today):
+ """`today` does not match rule."""
+ # No last date
+ expected = today.replace(day=23)
+ assert expected == get_date(True, RULE_FRIDAYS, tz=UTC)[1]
+ assert expected == get_date(True, RULE_FRIDAYS, tz=UTC)[1]
+
+ # Last date in the past; doesn't match rule
+ date = today.replace(hour=7)
+ expected = today.replace(day=23, hour=7)
+ assert expected == get_date(True, RULE_FRIDAYS, date, tz=UTC)[1]
+
+ # Last date in the future; doesn't match rule
+ date = today.replace(day=24, hour=7)
+ expected = today.replace(day=30, hour=7)
+ assert expected == get_date(True, RULE_FRIDAYS, date, tz=UTC)[1]
+
+
+def test_current_time_matching_rule(today):
+ # Last date in the past; matches rule
+ date = today.replace(day=16, hour=8)
+ expected = today.replace(day=23, hour=8)
+ assert expected == get_date(True, RULE_FRIDAYS, date, tz=UTC)[1]
+
+ # Last date in the future; matches rule
+ date = today.replace(day=23, hour=18)
+ expected = today.replace(day=30, hour=18)
+ assert expected == get_date(True, RULE_FRIDAYS, date, tz=UTC)[1]
+
+
+@pytest.mark.parametrize("scheduling", [True, False])
+def test_current_time_matching_rule_no_given_date(now, scheduling):
+ """
+ No last date given means we should always get the current time.
+
+ `now` matches the rule.
+ """
+ assert now == get_date(scheduling, RULE_THURSDAYS, tz=UTC)[1]
+
+
+def test_last_date_in_the_past_not_matching_rule(today):
+ """Last date in the past; doesn't match rule."""
+ # Corresponding time has already passed, today
+ date = today.replace(day=21, hour=7)
+ expected = today.replace(day=29, hour=7)
+ assert expected == get_date(True, RULE_THURSDAYS, date, tz=UTC)[1]
+
+ # Corresponding time has not passed today
+ date = today.replace(day=21, hour=18)
+ expected = today.replace(day=22, hour=18)
+ assert expected == get_date(True, RULE_THURSDAYS, date, tz=UTC)[1]
+
+
+def test_last_date_in_the_future_not_matching_rule(today):
+ """Last date in the future; doesn't match rule."""
+ # Corresponding time has already passed, today
+ date = today.replace(day=24, hour=7)
+ expected = today.replace(day=29, hour=7)
+ assert expected == get_date(True, RULE_THURSDAYS, date, tz=UTC)[1]
+
+ # Corresponding time has not passed today
+ date = today.replace(day=24, hour=18)
+ expected = today.replace(day=29, hour=18)
+ assert expected == get_date(True, RULE_THURSDAYS, date, tz=UTC)[1]
+
+
+def test_last_date_in_the_past_matching_rule(today):
+ """Last date in the past; matches rule."""
+ # Corresponding time has already passed, today
+ date = today.replace(day=15, hour=7)
+ expected = today.replace(day=29, hour=7)
+ assert expected == get_date(True, RULE_THURSDAYS, date, tz=UTC)[1]
+
+ # Corresponding time has already passed, today; rule specifies HOUR
+ date = today.replace(day=15, hour=7)
+ expected = today.replace(day=29, hour=9)
+ assert expected == get_date(True, RULE_THURSDAYS + ";BYHOUR=9", date, tz=UTC)[1]
+
+ # Corresponding time has not passed today
+ date = today.replace(day=15, hour=18)
+ expected = today.replace(day=22, hour=18)
+ assert expected == get_date(True, RULE_THURSDAYS, date, tz=UTC)[1]
+
+
+def test_last_date_in_the_future_matching_rule(today):
+ """Last date in the future; matches rule."""
+ # Corresponding time has already passed, today
+ date = today.replace(day=29, hour=7)
+ expected = today.replace(day=5, month=9, hour=7)
+ assert expected == get_date(True, RULE_THURSDAYS, date, tz=UTC)[1]
+
+ # Corresponding time has not passed today
+ date = today.replace(day=22, hour=18)
+ expected = today.replace(day=29, hour=18)
+ assert expected == get_date(True, RULE_THURSDAYS, date, tz=UTC)[1]
+
+
+@pytest.fixture
+def today(now):
+ current_time = now.strftime("%Y-%m-%d %H:%M:%S %Z")
+ yield dateutil.parser.parse(current_time)
+
+
+@pytest.fixture
+def now() -> datetime:
+ """
+ Get the current time.
+
+ datetime is frozen to this point in time.
+ """
+ _NOW = datetime.datetime(2013, 8, 22, 10, 0, 0, tzinfo=UTC) # Thursday
+
+ with freeze_time(_NOW):
+ yield _NOW
diff --git a/tests/test_shortcodes.py b/tests/test_shortcodes.py
new file mode 100644
index 0000000..8383176
--- /dev/null
+++ b/tests/test_shortcodes.py
@@ -0,0 +1,200 @@
+"""Test shortcodes."""
+
+import pytest
+
+import nikola.utils
+from nikola import shortcodes
+
+
+@pytest.mark.parametrize(
+ "template, expected_result",
+ [
+ ("test({{% noargs %}})", "test(noargs success!)"),
+ (
+ "test({{% noargs %}}\\hello world/{{% /noargs %}})",
+ "test(noargs \\hello world/ success!)",
+ ),
+ ],
+)
+def test_noargs(site, template, expected_result):
+ applied_shortcode = shortcodes.apply_shortcodes(template, site.shortcode_registry)[0]
+ assert applied_shortcode == expected_result
+
+
+@pytest.mark.parametrize(
+ "template, expected_result",
+ [
+ ("test({{% arg 1 %}})", "test(arg ('1',)/[]/)"),
+ ("test({{% arg 1 2aa %}})", "test(arg ('1', '2aa')/[]/)"),
+ ('test({{% arg "hello world" %}})', "test(arg ('hello world',)/[]/)"),
+ ("test({{% arg back\\ slash arg2 %}})", "test(arg ('back slash', 'arg2')/[]/)"),
+ ('test({{% arg "%}}" %}})', "test(arg ('%}}',)/[]/)"),
+ ],
+)
+def test_positional_arguments(site, template, expected_result):
+ applied_shortcode = shortcodes.apply_shortcodes(template, site.shortcode_registry)[0]
+ assert applied_shortcode == expected_result
+
+
+@pytest.mark.parametrize(
+ "template, expected_result",
+ [
+ ("test({{% arg 1a=2b %}})", "test(arg ()/[('1a', '2b')]/)"),
+ (
+ 'test({{% arg 1a="2b 3c" 4d=5f %}})',
+ "test(arg ()/[('1a', '2b 3c'), ('4d', '5f')]/)",
+ ),
+ (
+ 'test({{% arg 1a="2b 3c" 4d=5f back=slash\\ slash %}})',
+ "test(arg ()/[('1a', '2b 3c'), ('4d', '5f'), ('back', 'slash slash')]/)",
+ ),
+ ],
+)
+def test_arg_keyword(site, template, expected_result):
+ applied_shortcode = shortcodes.apply_shortcodes(template, site.shortcode_registry)[0]
+ assert applied_shortcode == expected_result
+
+
+@pytest.mark.parametrize(
+ "template, expected_result",
+ [
+ ("test({{% arg 123 %}}Hello!{{% /arg %}})", "test(arg ('123',)/[]/Hello!)"),
+ (
+ "test({{% arg 123 456 foo=bar %}}Hello world!{{% /arg %}})",
+ "test(arg ('123', '456')/[('foo', 'bar')]/Hello world!)",
+ ),
+ (
+ 'test({{% arg 123 456 foo=bar baz="quotes rock." %}}Hello test suite!{{% /arg %}})',
+ "test(arg ('123', '456')/[('baz', 'quotes rock.'), ('foo', 'bar')]/Hello test suite!)",
+ ),
+ (
+ 'test({{% arg "123 foo" foobar foo=bar baz="quotes rock." %}}Hello test suite!!{{% /arg %}})',
+ "test(arg ('123 foo', 'foobar')/[('baz', 'quotes rock.'), ('foo', 'bar')]/Hello test suite!!)",
+ ),
+ ],
+)
+def test_data(site, template, expected_result):
+ applied_shortcode = shortcodes.apply_shortcodes(template, site.shortcode_registry)[0]
+ assert applied_shortcode == expected_result
+
+
+@pytest.mark.parametrize(
+ "template, expected_error_pattern",
+ [
+ (
+ "{{% start",
+ "^Shortcode 'start' starting at .* is not terminated correctly with '%}}'!",
+ ),
+ (
+ "{{% wrong ending %%}",
+ "^Syntax error in shortcode 'wrong' at .*: expecting whitespace!",
+ ),
+ (
+ "{{% start %}} {{% /end %}}",
+ "^Found shortcode ending '{{% /end %}}' which isn't closing a started shortcode",
+ ),
+ ('{{% start "asdf %}}', "^Unexpected end of unquoted string"),
+ ("{{% start =b %}}", "^String starting at .* must be non-empty!"),
+ ('{{% start "a\\', "^Unexpected end of data while escaping"),
+ ("{{% start a\\", "^Unexpected end of data while escaping"),
+ ('{{% start a"b" %}}', "^Unexpected quotation mark in unquoted string"),
+ (
+ '{{% start "a"b %}}',
+ "^Syntax error in shortcode 'start' at .*: expecting whitespace!",
+ ),
+ ("{{% %}}", "^Syntax error: '{{%' must be followed by shortcode name"),
+ ("{{%", "^Syntax error: '{{%' must be followed by shortcode name"),
+ ("{{% ", "^Syntax error: '{{%' must be followed by shortcode name"),
+ (
+ "{{% / %}}",
+ "^Found shortcode ending '{{% / %}}' which isn't closing a started shortcode",
+ ),
+ ("{{% / a %}}", "^Syntax error: '{{% /' must be followed by ' %}}'"),
+ (
+ "==> {{% <==",
+ "^Shortcode '<==' starting at .* is not terminated correctly with '%}}'!",
+ ),
+ ],
+)
+def test_errors(site, template, expected_error_pattern):
+ with pytest.raises(shortcodes.ParsingError, match=expected_error_pattern):
+ shortcodes.apply_shortcodes(
+ template, site.shortcode_registry, raise_exceptions=True
+ )
+
+
+@pytest.mark.parametrize(
+ "input, expected",
+ [
+ ("{{% foo %}}", (u"SC1", {u"SC1": u"{{% foo %}}"})),
+ (
+ "{{% foo %}} bar {{% /foo %}}",
+ (u"SC1", {u"SC1": u"{{% foo %}} bar {{% /foo %}}"}),
+ ),
+ (
+ "AAA{{% foo %}} bar {{% /foo %}}BBB",
+ (u"AAASC1BBB", {u"SC1": u"{{% foo %}} bar {{% /foo %}}"}),
+ ),
+ (
+ "AAA{{% foo %}} {{% bar %}} {{% /foo %}}BBB",
+ (u"AAASC1BBB", {u"SC1": u"{{% foo %}} {{% bar %}} {{% /foo %}}"}),
+ ),
+ (
+ "AAA{{% foo %}} {{% /bar %}} {{% /foo %}}BBB",
+ (u"AAASC1BBB", {u"SC1": u"{{% foo %}} {{% /bar %}} {{% /foo %}}"}),
+ ),
+ (
+ "AAA{{% foo %}} {{% bar %}} quux {{% /bar %}} {{% /foo %}}BBB",
+ (
+ u"AAASC1BBB",
+ {u"SC1": u"{{% foo %}} {{% bar %}} quux {{% /bar %}} {{% /foo %}}"},
+ ),
+ ),
+ (
+ "AAA{{% foo %}} BBB {{% bar %}} quux {{% /bar %}} CCC",
+ (
+ u"AAASC1 BBB SC2 CCC",
+ {u"SC1": u"{{% foo %}}", u"SC2": u"{{% bar %}} quux {{% /bar %}}"},
+ ),
+ ),
+ ],
+)
+def test_extract_shortcodes(input, expected, monkeypatch):
+ i = iter("SC%d" % i for i in range(1, 100))
+ monkeypatch.setattr(shortcodes, "_new_sc_id", i.__next__)
+ extracted = shortcodes.extract_shortcodes(input)
+ assert extracted == expected
+
+
+@pytest.fixture(scope="module")
+def site():
+ s = FakeSiteWithShortcodeRegistry()
+ s.register_shortcode("noargs", noargs)
+ s.register_shortcode("arg", arg)
+ return s
+
+
+class FakeSiteWithShortcodeRegistry:
+ def __init__(self):
+ self.shortcode_registry = {}
+ self.debug = True
+
+ # this code duplicated in nikola/nikola.py
+ def register_shortcode(self, name, f):
+ """Register function f to handle shortcode "name"."""
+ if name in self.shortcode_registry:
+ nikola.utils.LOGGER.warn("Shortcode name conflict: %s", name)
+ return
+ self.shortcode_registry[name] = f
+
+
+def noargs(site, data="", lang=""):
+ return "noargs {0} success!".format(data)
+
+
+def arg(*args, **kwargs):
+ # don’t clutter the kwargs dict
+ kwargs.pop("site")
+ data = kwargs.pop("data")
+ kwargs.pop("lang")
+ return "arg {0}/{1}/{2}".format(args, sorted(kwargs.items()), data)
diff --git a/tests/test_slugify.py b/tests/test_slugify.py
index 9dec5a6..a60896f 100644
--- a/tests/test_slugify.py
+++ b/tests/test_slugify.py
@@ -1,65 +1,73 @@
-# -*- coding: utf-8 -*-
+"""Test slugify."""
-u"""Test slugify."""
+import pytest
-from __future__ import unicode_literals
import nikola.utils
-def test_ascii():
- """Test an ASCII-only string."""
- o = nikola.utils.slugify(u'hello')
- assert o == u'hello'
- assert isinstance(o, nikola.utils.unicode_str)
-
-
-def test_ascii_dash():
- """Test an ASCII string, with dashes."""
- o = nikola.utils.slugify(u'hello-world')
- assert o == u'hello-world'
- assert isinstance(o, nikola.utils.unicode_str)
-
-
-def test_ascii_fancy():
- """Test an ASCII string, with fancy characters."""
- o = nikola.utils.slugify(u'The quick brown fox jumps over the lazy dog!-123.456')
- assert o == u'the-quick-brown-fox-jumps-over-the-lazy-dog-123456'
- assert isinstance(o, nikola.utils.unicode_str)
-
-
-def test_pl():
- """Test a string with Polish diacritical characters."""
- o = nikola.utils.slugify(u'zażółćgęśląjaźń')
- assert o == u'zazolcgeslajazn'
- assert isinstance(o, nikola.utils.unicode_str)
-
-
-def test_pl_dash():
- """Test a string with Polish diacritical characters and dashes."""
- o = nikola.utils.slugify(u'zażółć-gęślą-jaźń')
- assert o == u'zazolc-gesla-jazn'
-
-
-def test_pl_fancy():
- """Test a string with Polish diacritical characters and fancy characters."""
- o = nikola.utils.slugify(u'Zażółć gęślą jaźń!-123.456')
- assert o == u'zazolc-gesla-jazn-123456'
- assert isinstance(o, nikola.utils.unicode_str)
-
-
-def test_disarmed():
+@pytest.mark.parametrize(
+ "title, language, expected_slug",
+ [
+ pytest.param("hello", "en", "hello", id="ASCII"),
+ pytest.param("hello-world", "en", "hello-world", id="ASCII with dashes"),
+ pytest.param("hello world", "en", "hello-world", id="ASCII two words"),
+ pytest.param("Hello World", "en", "hello-world", id="ASCII uppercase"),
+ pytest.param(
+ "The quick brown fox jumps over the lazy dog!-123.456",
+ "en",
+ "the-quick-brown-fox-jumps-over-the-lazy-dog-123456",
+ id="ASCII with fancy characters",
+ ),
+ pytest.param(
+ "zażółćgęśląjaźń",
+ "pl",
+ "zazolcgeslajazn",
+ id="Polish diacritical characters",
+ ),
+ pytest.param(
+ "zażółć-gęślą-jaźń",
+ "pl",
+ "zazolc-gesla-jazn",
+ id="Polish diacritical characters and dashes",
+ ),
+ pytest.param(
+ "Zażółć gęślą jaźń!-123.456",
+ "pl",
+ "zazolc-gesla-jazn-123456",
+ id="Polish diacritical characters and fancy characters",
+ ),
+ ],
+)
+def test_slugify(title, language, expected_slug):
+ o = nikola.utils.slugify(title, lang=language)
+ assert o == expected_slug
+ assert isinstance(o, str)
+
+
+@pytest.mark.parametrize(
+ "title, expected_slug",
+ [
+ pytest.param(
+ u"Zażółć gęślą jaźń!-123.456", u"Zażółć gęślą jaźń!-123.456", id="polish"
+ ),
+ pytest.param(
+ u'Zażółć gęślą jaźń!-123.456 "Hello World"?#H<e>l/l\\o:W\'o\rr*l\td|!\n',
+ u"Zażółć gęślą jaźń!-123.456 -Hello World---H-e-l-l-o-W-o-r-l-d-!-",
+ id="polish with banned characters",
+ ),
+ ],
+)
+def test_disarmed(disarm_slugify, title, expected_slug):
"""Test disarmed slugify."""
- nikola.utils.USE_SLUGIFY = False
- o = nikola.utils.slugify(u'Zażółć gęślą jaźń!-123.456')
- assert o == u'Zażółć gęślą jaźń!-123.456'
- assert isinstance(o, nikola.utils.unicode_str)
- nikola.utils.USE_SLUGIFY = True
+ o = nikola.utils.slugify(title, lang="pl")
+ assert o == expected_slug
+ assert isinstance(o, str)
-def test_disarmed_weird():
- """Test disarmed slugify with banned characters."""
+@pytest.fixture
+def disarm_slugify():
nikola.utils.USE_SLUGIFY = False
- o = nikola.utils.slugify(u'Zażółć gęślą jaźń!-123.456 "Hello World"?#H<e>l/l\\o:W\'o\rr*l\td|!\n')
- assert o == u'Zażółć gęślą jaźń!-123.456 -Hello World---H-e-l-l-o-W-o-r-l-d-!-'
- assert isinstance(o, nikola.utils.unicode_str)
- nikola.utils.USE_SLUGIFY = True
+ try:
+ yield
+ finally:
+ nikola.utils.USE_SLUGIFY = True
diff --git a/tests/test_task_scale_images.py b/tests/test_task_scale_images.py
new file mode 100644
index 0000000..46a39a4
--- /dev/null
+++ b/tests/test_task_scale_images.py
@@ -0,0 +1,126 @@
+import os
+from tempfile import NamedTemporaryFile
+
+import pytest
+from PIL import Image, ImageDraw
+
+from nikola.plugins.task import scale_images
+
+# These tests don't require valid profiles. They need only to verify
+# that profile data is/isn't saved with images.
+# It would be nice to use PIL.ImageCms to create valid profiles, but
+# in many Pillow distributions ImageCms is a stub.
+# ICC file data format specification:
+# http://www.color.org/icc32.pdf
+PROFILE = b"invalid profile data"
+
+
+def test_handling_icc_profiles(test_images, destination_dir):
+ filename, expected_profile = test_images
+
+ pathname = os.path.join(str(destination_dir), filename)
+ assert os.path.exists(pathname), pathname
+
+ img = Image.open(pathname)
+ actual_profile = img.info.get("icc_profile")
+ assert actual_profile == expected_profile
+
+
+@pytest.fixture(
+ params=[
+ pytest.param(True, id="with icc filename"),
+ pytest.param(False, id="without icc filename"),
+ ]
+)
+def test_images(request, preserve_icc_profiles, source_dir, site):
+ image_filename = create_src_image(str(source_dir), request.param)
+ run_task(site)
+
+ if request.param:
+ yield image_filename, PROFILE if preserve_icc_profiles else None
+ else:
+ yield image_filename, None
+
+
+@pytest.fixture(
+ params=[
+ pytest.param(True, id="profiles preserved"),
+ pytest.param(False, id="profiles not preserved"),
+ ]
+)
+def preserve_icc_profiles(request):
+ return request.param
+
+
+@pytest.fixture
+def source_dir(tmpdir_factory):
+ return tmpdir_factory.mktemp("image_source")
+
+
+@pytest.fixture
+def site(preserve_icc_profiles, source_dir, destination_dir):
+ config = {
+ "IMAGE_FOLDERS": {str(source_dir): ""},
+ "OUTPUT_FOLDER": str(destination_dir),
+ "IMAGE_THUMBNAIL_SIZE": 128,
+ "IMAGE_THUMBNAIL_FORMAT": "{name}.thumbnail{ext}",
+ "MAX_IMAGE_SIZE": 512,
+ "FILTERS": {},
+ "PRESERVE_EXIF_DATA": False,
+ "EXIF_WHITELIST": {},
+ "PRESERVE_ICC_PROFILES": preserve_icc_profiles,
+ }
+ return FakeSite(config)
+
+
+class FakeSite:
+ def __init__(self, config):
+ self.config = config
+ self.debug = True
+
+
+@pytest.fixture
+def destination_dir(tmpdir_factory):
+ return tmpdir_factory.mktemp("image_output")
+
+
+def run_task(site):
+ task_instance = get_task_instance(site)
+ for task in task_instance.gen_tasks():
+ for action, args in task.get("actions", []):
+ action(*args)
+
+
+def get_task_instance(site):
+ result = scale_images.ScaleImage()
+ result.set_site(site)
+ return result
+
+
+def create_src_image(testdir, use_icc_profile):
+ img = create_test_image()
+ pathname = tmp_img_name(testdir)
+
+ # Test two variants: with and without an associated icc_profile
+ if use_icc_profile:
+ img.save(pathname, icc_profile=PROFILE)
+ else:
+ img.save(pathname)
+
+ return os.path.basename(pathname)
+
+
+def create_test_image():
+ # Make a white image with a red stripe on the diagonal.
+ width = 64
+ height = 64
+ img = Image.new("RGB", (width, height), (255, 255, 255))
+ draw = ImageDraw.Draw(img)
+ draw.line((0, 0, width, height), fill=(255, 128, 128))
+ draw.line((width, 0, 0, height), fill=(128, 128, 255))
+ return img
+
+
+def tmp_img_name(dirname):
+ pathname = NamedTemporaryFile(suffix=".jpg", dir=dirname, delete=False)
+ return pathname.name
diff --git a/tests/test_template_shortcodes.py b/tests/test_template_shortcodes.py
new file mode 100644
index 0000000..c6c948d
--- /dev/null
+++ b/tests/test_template_shortcodes.py
@@ -0,0 +1,78 @@
+"""Test template-based shortcodes."""
+
+import pytest
+
+from nikola import Nikola
+
+
+def test_mixedargs(site):
+ test_template = """
+arg1: {{ _args[0] }}
+arg2: {{ _args[1] }}
+kwarg1: {{ kwarg1 }}
+kwarg2: {{ kwarg2 }}
+"""
+
+ site.shortcode_registry["test1"] = site._make_renderfunc(test_template)
+ site.shortcode_registry["test2"] = site._make_renderfunc(
+ "Something completely different"
+ )
+
+ res = site.apply_shortcodes("{{% test1 kwarg1=spamm arg1 kwarg2=foo,bar arg2 %}}")[
+ 0
+ ]
+
+ assert res.strip() == """
+arg1: arg1
+arg2: arg2
+kwarg1: spamm
+kwarg2: foo,bar""".strip()
+
+
+@pytest.mark.parametrize(
+ "template, data, expected_result",
+ [
+ # one argument
+ ("arg={{ _args[0] }}", "{{% test1 onearg %}}", "arg=onearg"),
+ ("arg={{ _args[0] }}", '{{% test1 "one two" %}}', "arg=one two"),
+ # keyword arguments
+ ("foo={{ foo }}", "{{% test1 foo=bar %}}", "foo=bar"),
+ ("foo={{ foo }}", '{{% test1 foo="bar baz" %}}', "foo=bar baz"),
+ ("foo={{ foo }}", '{{% test1 foo="bar baz" spamm=ham %}}', "foo=bar baz"),
+ # data
+ (
+ "data={{ data }}",
+ "{{% test1 %}}spamm spamm{{% /test1 %}}",
+ "data=spamm spamm",
+ ),
+ ("data={{ data }}", "{{% test1 spamm %}}", "data="),
+ ("data={{ data }}", "{{% test1 data=dummy %}}", "data="),
+ ],
+)
+def test_applying_shortcode(site, template, data, expected_result):
+ site.shortcode_registry["test1"] = site._make_renderfunc(template)
+
+ assert site.apply_shortcodes(data)[0] == expected_result
+
+
+@pytest.fixture(scope="module")
+def site():
+ s = ShortcodeFakeSite()
+ s.init_plugins()
+ s._template_system = None
+ return s
+
+
+class ShortcodeFakeSite(Nikola):
+ def _get_template_system(self):
+ if self._template_system is None:
+ # Load template plugin
+ self._template_system = self.plugin_manager.getPluginByName(
+ "jinja", "TemplateSystem"
+ ).plugin_object
+ self._template_system.set_directories(".", "cache")
+ self._template_system.set_site(self)
+
+ return self._template_system
+
+ template_system = property(_get_template_system)
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 9507de2..997d520 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,335 +1,591 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
+"""
+Testing Nikolas utility functions.
+"""
import os
-import sys
+from unittest import mock
-
-import unittest
-import mock
+import pytest
import lxml.html
+
+from nikola import metadata_extractors
+from nikola.plugins.task.sitemap import get_base_path as sitemap_get_base_path
from nikola.post import get_meta
-from nikola.utils import demote_headers, TranslatableSetting
-
-
-class dummy(object):
- pass
-
-
-class GetMetaTest(unittest.TestCase):
- def test_getting_metadata_from_content(self):
- file_metadata = ".. title: Nikola needs more tests!\n"\
- ".. slug: write-tests-now\n"\
- ".. date: 2012/09/15 19:52:05\n"\
- ".. tags:\n"\
- ".. link:\n"\
- ".. description:\n"\
- "Post content\n"
-
- opener_mock = mock.mock_open(read_data=file_metadata)
-
- post = dummy()
- post.source_path = 'file_with_metadata'
- post.metadata_path = 'file_with_metadata.meta'
-
- with mock.patch('nikola.post.io.open', opener_mock, create=True):
- meta, nsm = get_meta(post)
-
- self.assertEqual('Nikola needs more tests!', meta['title'])
- self.assertEqual('write-tests-now', meta['slug'])
- self.assertEqual('2012/09/15 19:52:05', meta['date'])
- self.assertFalse('tags' in meta)
- self.assertFalse('link' in meta)
- self.assertFalse('description' in meta)
- self.assertTrue(nsm)
-
- def test_get_title_from_rest(self):
- file_metadata = ".. slug: write-tests-now\n"\
- ".. date: 2012/09/15 19:52:05\n"\
- ".. tags:\n"\
- ".. link:\n"\
- ".. description:\n\n"\
- "Post Title\n"\
- "----------\n"
-
- opener_mock = mock.mock_open(read_data=file_metadata)
-
- post = dummy()
- post.source_path = 'file_with_metadata'
- post.metadata_path = 'file_with_metadata.meta'
-
- with mock.patch('nikola.post.io.open', opener_mock, create=True):
- meta, nsm = get_meta(post)
-
- self.assertEqual('Post Title', meta['title'])
- self.assertEqual('write-tests-now', meta['slug'])
- self.assertEqual('2012/09/15 19:52:05', meta['date'])
- self.assertFalse('tags' in meta)
- self.assertFalse('link' in meta)
- self.assertFalse('description' in meta)
- self.assertTrue(nsm)
-
- def test_get_title_from_fname(self):
- file_metadata = ".. slug: write-tests-now\n"\
- ".. date: 2012/09/15 19:52:05\n"\
- ".. tags:\n"\
- ".. link:\n"\
- ".. description:\n"
-
- opener_mock = mock.mock_open(read_data=file_metadata)
-
- post = dummy()
- post.source_path = 'file_with_metadata'
- post.metadata_path = 'file_with_metadata.meta'
-
- with mock.patch('nikola.post.io.open', opener_mock, create=True):
- meta, nsm = get_meta(post, 'file_with_metadata')
-
- self.assertEqual('file_with_metadata', meta['title'])
- self.assertEqual('write-tests-now', meta['slug'])
- self.assertEqual('2012/09/15 19:52:05', meta['date'])
- self.assertFalse('tags' in meta)
- self.assertFalse('link' in meta)
- self.assertFalse('description' in meta)
- self.assertTrue(nsm)
-
- def test_use_filename_as_slug_fallback(self):
- file_metadata = ".. title: Nikola needs more tests!\n"\
- ".. date: 2012/09/15 19:52:05\n"\
- ".. tags:\n"\
- ".. link:\n"\
- ".. description:\n\n"\
- "Post content\n"
-
- opener_mock = mock.mock_open(read_data=file_metadata)
-
- post = dummy()
- post.source_path = 'Slugify this'
- post.metadata_path = 'Slugify this.meta'
-
- with mock.patch('nikola.post.io.open', opener_mock, create=True):
- meta, nsm = get_meta(post, 'Slugify this')
- self.assertEqual('Nikola needs more tests!', meta['title'])
- self.assertEqual('slugify-this', meta['slug'])
- self.assertEqual('2012/09/15 19:52:05', meta['date'])
- self.assertFalse('tags' in meta)
- self.assertFalse('link' in meta)
- self.assertFalse('description' in meta)
- self.assertTrue(nsm)
-
- def test_extracting_metadata_from_filename(self):
- post = dummy()
- post.source_path = '2013-01-23-the_slug-dubdubtitle.md'
- post.metadata_path = '2013-01-23-the_slug-dubdubtitle.meta'
- with mock.patch('nikola.post.io.open', create=True):
- meta, _ = get_meta(
- post,
- '(?P<date>\d{4}-\d{2}-\d{2})-(?P<slug>.*)-(?P<title>.*)\.md')
-
- self.assertEqual('dubdubtitle', meta['title'])
- self.assertEqual('the_slug', meta['slug'])
- self.assertEqual('2013-01-23', meta['date'])
-
- def test_get_meta_slug_only_from_filename(self):
- post = dummy()
- post.source_path = 'some/path/the_slug.md'
- post.metadata_path = 'some/path/the_slug.meta'
- with mock.patch('nikola.post.io.open', create=True):
- meta, _ = get_meta(post)
-
- self.assertEqual('the_slug', meta['slug'])
-
-
-class HeaderDemotionTest(unittest.TestCase):
- def demote_by_zero(self):
- input_str = '''\
- <h1>header 1</h1>
- <h2>header 2</h2>
- <h3>header 3</h3>
- <h4>header 4</h4>
- <h5>header 5</h5>
- <h6>header 6</h6>
- '''
- expected_output = '''\
- <h1>header 1</h1>
- <h2>header 2</h2>
- <h3>header 3</h3>
- <h4>header 4</h4>
- <h5>header 5</h5>
- <h6>header 6</h6>
- '''
- doc = lxml.html.fromstring(input_str)
- outdoc = lxml.html.fromstring(expected_output)
- demote_headers(doc, 0)
- self.assertEquals(lxml.html.tostring(outdoc), lxml.html.tostring(doc))
-
- def demote_by_one(self):
- input_str = '''\
- <h1>header 1</h1>
- <h2>header 2</h2>
- <h3>header 3</h3>
- <h4>header 4</h4>
- <h5>header 5</h5>
- <h6>header 6</h6>
- '''
- expected_output = '''\
- <h2>header 1</h2>
- <h3>header 2</h3>
- <h4>header 3</h4>
- <h5>header 4</h5>
- <h6>header 5</h6>
- <h6>header 6</h6>
- '''
- doc = lxml.html.fromstring(input_str)
- outdoc = lxml.html.fromstring(expected_output)
- demote_headers(doc, 1)
- self.assertEquals(lxml.html.tostring(outdoc), lxml.html.tostring(doc))
-
- def demote_by_two(self):
- input_str = '''\
- <h1>header 1</h1>
- <h2>header 2</h2>
- <h3>header 3</h3>
- <h4>header 4</h4>
- <h5>header 5</h5>
- <h6>header 6</h6>
- '''
- expected_output = '''\
- <h3>header 1</h3>
- <h4>header 2</h4>
- <h5>header 3</h5>
- <h6>header 4</h6>
- <h6>header 5</h6>
- <h6>header 6</h6>
- '''
- doc = lxml.html.fromstring(input_str)
- outdoc = lxml.html.fromstring(expected_output)
- demote_headers(doc, 2)
- self.assertEquals(lxml.html.tostring(outdoc), lxml.html.tostring(doc))
-
- def demote_by_minus_one(self):
- input_str = '''\
- <h1>header 1</h1>
- <h2>header 2</h2>
- <h3>header 3</h3>
- <h4>header 4</h4>
- <h5>header 5</h5>
- <h6>header 6</h6>
- '''
- expected_output = '''\
- <h1>header 1</h1>
- <h1>header 2</h1>
- <h2>header 3</h2>
- <h3>header 4</h3>
- <h4>header 5</h4>
- <h5>header 6</h5>
- '''
- doc = lxml.html.fromstring(input_str)
- outdoc = lxml.html.fromstring(expected_output)
- demote_headers(doc, -1)
- self.assertEquals(lxml.html.tostring(outdoc), lxml.html.tostring(doc))
-
-
-class TranslatableSettingsTest(unittest.TestCase):
- """Tests for translatable settings."""
-
- def test_string_input(self):
- """Tests for string input."""
- inp = 'Fancy Blog'
- S = TranslatableSetting('S', inp, {'xx': ''})
- S.default_lang = 'xx'
- S.lang = 'xx'
-
- try:
- u = unicode(S)
- except NameError: # Python 3
- u = str(S)
-
- cn = S() # no language specified
- cr = S('xx') # real language specified
- cf = S('zz') # fake language specified
-
- self.assertEqual(inp, u)
- self.assertEqual(inp, cn)
- self.assertEqual(inp, cr)
- self.assertEqual(inp, cf)
- self.assertEqual(S.lang, 'xx')
- self.assertEqual(S.default_lang, 'xx')
-
- def test_dict_input(self):
- """Tests for dict input."""
- inp = {'xx': 'Fancy Blog',
- 'zz': 'Schmancy Blog'}
-
- S = TranslatableSetting('S', inp, {'xx': '', 'zz': ''})
- S.default_lang = 'xx'
- S.lang = 'xx'
-
- try:
- u = unicode(S)
- except NameError: # Python 3
- u = str(S)
-
- cn = S()
- cx = S('xx')
- cz = S('zz')
- cf = S('ff')
-
- self.assertEqual(inp['xx'], u)
- self.assertEqual(inp['xx'], cn)
- self.assertEqual(inp['xx'], cx)
- self.assertEqual(inp['zz'], cz)
- self.assertEqual(inp['xx'], cf)
-
- def test_dict_input_lang(self):
- """Test dict input, with a language change along the way."""
- inp = {'xx': 'Fancy Blog',
- 'zz': 'Schmancy Blog'}
-
- S = TranslatableSetting('S', inp, {'xx': '', 'zz': ''})
- S.default_lang = 'xx'
- S.lang = 'xx'
-
- try:
- u = unicode(S)
- except NameError: # Python 3
- u = str(S)
-
- cn = S()
-
- self.assertEqual(inp['xx'], u)
- self.assertEqual(inp['xx'], cn)
-
- # Change the language.
- # WARNING: DO NOT set lang locally in real code! Set it globally
- # instead! (TranslatableSetting.lang = ...)
- # WARNING: TranslatableSetting.lang is used to override the current
- # locale settings returned by LocaleBorg! Use with care!
- S.lang = 'zz'
-
- try:
- u = unicode(S)
- except NameError: # Python 3
- u = str(S)
-
- cn = S()
-
- self.assertEqual(inp['zz'], u)
- self.assertEqual(inp['zz'], cn)
-
-
-def test_get_metadata_from_file():
- # These were doctests and not running :-P
- from nikola.post import _get_metadata_from_file
- g = _get_metadata_from_file
- assert list(g([]).values()) == []
- assert str(g(["======","FooBar","======"])["title"]) == 'FooBar'
- assert str(g(["FooBar","======"])["title"]) == 'FooBar'
- assert str(g(["#FooBar"])["title"]) == 'FooBar'
- assert str(g([".. title: FooBar"])["title"]) == 'FooBar'
- assert 'title' not in g(["","",".. title: FooBar"])
- assert 'title' in g(["",".. title: FooBar"])
- assert 'title' in g([".. foo: bar","","FooBar", "------"])
-
-if __name__ == '__main__':
- unittest.main()
+from nikola.utils import (
+ TemplateHookRegistry,
+ TranslatableSetting,
+ demote_headers,
+ get_asset_path,
+ get_crumbs,
+ get_theme_chain,
+ get_translation_candidate,
+ write_metadata,
+)
+
+
+def test_getting_metadata_from_content(post):
+ post.source_path = "file_with_metadata"
+ post.metadata_path = "file_with_metadata.meta"
+
+ file_content = """\
+.. title: Nikola needs more tests!
+.. slug: write-tests-now
+.. date: 2012/09/15 19:52:05
+.. tags:
+.. link:
+.. description:
+
+Post content
+"""
+ opener_mock = mock.mock_open(read_data=file_content)
+ with mock.patch("nikola.post.io.open", opener_mock, create=True):
+ meta = get_meta(post, None)[0]
+
+ assert "Nikola needs more tests!" == meta["title"]
+ assert "write-tests-now" == meta["slug"]
+ assert "2012/09/15 19:52:05" == meta["date"]
+ assert "tags" not in meta
+ assert "link" not in meta
+ assert "description" not in meta
+
+
+def test_get_title_from_fname(post):
+ post.source_path = "file_with_metadata"
+ post.metadata_path = "file_with_metadata.meta"
+
+ file_content = """\
+.. slug: write-tests-now
+.. date: 2012/09/15 19:52:05
+.. tags:
+.. link:
+.. description:
+"""
+ opener_mock = mock.mock_open(read_data=file_content)
+ with mock.patch("nikola.post.io.open", opener_mock, create=True):
+ meta = get_meta(post, None)[0]
+
+ assert "file_with_metadata" == meta["title"]
+ assert "write-tests-now" == meta["slug"]
+ assert "2012/09/15 19:52:05" == meta["date"]
+ assert "tags" not in meta
+ assert "link" not in meta
+ assert "description" not in meta
+
+
+def test_use_filename_as_slug_fallback(post):
+ post.source_path = "Slugify this"
+ post.metadata_path = "Slugify this.meta"
+
+ file_content = """\
+.. title: Nikola needs more tests!
+.. date: 2012/09/15 19:52:05
+.. tags:
+.. link:
+.. description:
+
+Post content
+"""
+ opener_mock = mock.mock_open(read_data=file_content)
+ with mock.patch("nikola.post.io.open", opener_mock, create=True):
+ meta = get_meta(post, None)[0]
+
+ assert "Nikola needs more tests!" == meta["title"]
+ assert "slugify-this" == meta["slug"]
+ assert "2012/09/15 19:52:05" == meta["date"]
+ assert "tags" not in meta
+ assert "link" not in meta
+ assert "description" not in meta
+
+
+@pytest.mark.parametrize(
+ "unslugify, expected_title", [(True, "Dub dub title"), (False, "dub_dub_title")]
+)
+def test_extracting_metadata_from_filename(post, unslugify, expected_title):
+ post.source_path = "2013-01-23-the_slug-dub_dub_title.md"
+ post.metadata_path = "2013-01-23-the_slug-dub_dub_title.meta"
+
+ post.config[
+ "FILE_METADATA_REGEXP"
+ ] = r"(?P<date>\d{4}-\d{2}-\d{2})-(?P<slug>.*)-(?P<title>.*)\.md"
+ post.config["FILE_METADATA_UNSLUGIFY_TITLES"] = unslugify
+
+ no_metadata_opener = mock.mock_open(read_data="No metadata in the file!")
+ with mock.patch("nikola.post.io.open", no_metadata_opener, create=True):
+ meta = get_meta(post, None)[0]
+
+ assert expected_title == meta["title"]
+ assert "the_slug" == meta["slug"]
+ assert "2013-01-23" == meta["date"]
+
+
+def test_get_meta_slug_only_from_filename(post):
+ post.source_path = "some/path/the_slug.md"
+ post.metadata_path = "some/path/the_slug.meta"
+
+ no_metadata_opener = mock.mock_open(read_data="No metadata in the file!")
+ with mock.patch("nikola.post.io.open", no_metadata_opener, create=True):
+ meta = get_meta(post, None)[0]
+
+ assert "the_slug" == meta["slug"]
+
+
+@pytest.mark.parametrize(
+ "level, input_str, expected_output",
+ [
+ pytest.param(
+ 0,
+ """
+ <h1>header 1</h1>
+ <h2>header 2</h2>
+ <h3>header 3</h3>
+ <h4>header 4</h4>
+ <h5>header 5</h5>
+ <h6>header 6</h6>
+ """,
+ """
+ <h1>header 1</h1>
+ <h2>header 2</h2>
+ <h3>header 3</h3>
+ <h4>header 4</h4>
+ <h5>header 5</h5>
+ <h6>header 6</h6>
+ """,
+ id="by zero",
+ ),
+ pytest.param(
+ 1,
+ """
+ <h1>header 1</h1>
+ <h2>header 2</h2>
+ <h3>header 3</h3>
+ <h4>header 4</h4>
+ <h5>header 5</h5>
+ <h6>header 6</h6>
+ """,
+ """
+ <h2>header 1</h2>
+ <h3>header 2</h3>
+ <h4>header 3</h4>
+ <h5>header 4</h5>
+ <h6>header 5</h6>
+ <h6>header 6</h6>
+ """,
+ id="by one",
+ ),
+ pytest.param(
+ 2,
+ """
+ <h1>header 1</h1>
+ <h2>header 2</h2>
+ <h3>header 3</h3>
+ <h4>header 4</h4>
+ <h5>header 5</h5>
+ <h6>header 6</h6>
+ """,
+ """
+ <h3>header 1</h3>
+ <h4>header 2</h4>
+ <h5>header 3</h5>
+ <h6>header 4</h6>
+ <h6>header 5</h6>
+ <h6>header 6</h6>
+ """,
+ id="by two",
+ ),
+ pytest.param(
+ -1,
+ """
+ <h1>header 1</h1>
+ <h2>header 2</h2>
+ <h3>header 3</h3>
+ <h4>header 4</h4>
+ <h5>header 5</h5>
+ <h6>header 6</h6>
+ """,
+ """
+ <h1>header 1</h1>
+ <h1>header 2</h1>
+ <h2>header 3</h2>
+ <h3>header 4</h3>
+ <h4>header 5</h4>
+ <h5>header 6</h5>
+ """,
+ id="by minus one",
+ ),
+ ],
+)
+def test_demoting_headers(level, input_str, expected_output):
+ doc = lxml.html.fromstring(input_str)
+ outdoc = lxml.html.fromstring(expected_output)
+ demote_headers(doc, level)
+ assert lxml.html.tostring(outdoc) == lxml.html.tostring(doc)
+
+
+def test_TranslatableSettingsTest_with_string_input():
+ """Test ing translatable settings with string input."""
+ inp = "Fancy Blog"
+ setting = TranslatableSetting("TestSetting", inp, {"xx": ""})
+ setting.default_lang = "xx"
+ setting.lang = "xx"
+
+ assert inp == str(setting)
+ assert inp == setting() # no language specified
+ assert inp == setting("xx") # real language specified
+ assert inp == setting("zz") # fake language specified
+ assert setting.lang == "xx"
+ assert setting.default_lang == "xx"
+
+
+def test_TranslatableSetting_with_dict_input():
+ """Tests for translatable setting with dict input."""
+ inp = {"xx": "Fancy Blog", "zz": "Schmancy Blog"}
+
+ setting = TranslatableSetting("TestSetting", inp, {"xx": "", "zz": ""})
+ setting.default_lang = "xx"
+ setting.lang = "xx"
+
+ assert inp["xx"] == str(setting)
+ assert inp["xx"] == setting() # no language specified
+ assert inp["xx"] == setting("xx") # real language specified
+ assert inp["zz"] == setting("zz") # fake language specified
+ assert inp["xx"] == setting("ff")
+
+
+def test_TranslatableSetting_with_language_change():
+ """Test translatable setting with language change along the way."""
+ inp = {"xx": "Fancy Blog", "zz": "Schmancy Blog"}
+
+ setting = TranslatableSetting("TestSetting", inp, {"xx": "", "zz": ""})
+ setting.default_lang = "xx"
+ setting.lang = "xx"
+
+ assert inp["xx"] == str(setting)
+ assert inp["xx"] == setting()
+
+ # Change the language.
+ # WARNING: DO NOT set lang locally in real code! Set it globally
+ # instead! (TranslatableSetting.lang = ...)
+ # WARNING: TranslatableSetting.lang is used to override the current
+ # locale settings returned by LocaleBorg! Use with care!
+ setting.lang = "zz"
+
+ assert inp["zz"] == str(setting)
+ assert inp["zz"] == setting()
+
+
+@pytest.mark.parametrize(
+ "path, files_folders, expected_path_end",
+ [
+ (
+ "assets/css/nikola_rst.css",
+ {"files": ""}, # default files_folders
+ "nikola/data/themes/base/assets/css/nikola_rst.css",
+ ),
+ (
+ "assets/css/theme.css",
+ {"files": ""}, # default files_folders
+ "nikola/data/themes/bootstrap4/assets/css/theme.css",
+ ),
+ ("nikola.py", {"nikola": ""}, "nikola/nikola.py"),
+ ("nikola/nikola.py", {"nikola": "nikola"}, "nikola/nikola.py"),
+ ("nikola.py", {"nikola": "nikola"}, None),
+ ],
+)
+def test_get_asset_path(path, files_folders, expected_path_end):
+ theme_chain = get_theme_chain("bootstrap4", ["themes"])
+ asset_path = get_asset_path(path, theme_chain, files_folders)
+
+ if expected_path_end:
+ asset_path = asset_path.replace("\\", "/")
+ assert asset_path.endswith(expected_path_end)
+ else:
+ assert asset_path is None
+
+
+@pytest.mark.parametrize(
+ "path, is_file, expected_crumbs",
+ [
+ ("galleries", False, [["#", "galleries"]]),
+ (
+ os.path.join("galleries", "demo"),
+ False,
+ [["..", "galleries"], ["#", "demo"]],
+ ),
+ (
+ os.path.join("listings", "foo", "bar"),
+ True,
+ [["..", "listings"], [".", "foo"], ["#", "bar"]],
+ ),
+ ],
+)
+def test_get_crumbs(path, is_file, expected_crumbs):
+ crumbs = get_crumbs(path, is_file=is_file)
+ assert len(crumbs) == len(expected_crumbs)
+ for crumb, expected_crumb in zip(crumbs, expected_crumbs):
+ assert crumb == expected_crumb
+
+
+@pytest.mark.parametrize(
+ "pattern, path, lang, expected_path",
+ [
+ ("{path}.{lang}.{ext}", "*.rst", "es", "*.es.rst"),
+ ("{path}.{lang}.{ext}", "fancy.post.rst", "es", "fancy.post.es.rst"),
+ ("{path}.{lang}.{ext}", "*.es.rst", "es", "*.es.rst"),
+ ("{path}.{lang}.{ext}", "*.es.rst", "en", "*.rst"),
+ (
+ "{path}.{lang}.{ext}",
+ "cache/posts/fancy.post.es.html",
+ "en",
+ "cache/posts/fancy.post.html",
+ ),
+ (
+ "{path}.{lang}.{ext}",
+ "cache/posts/fancy.post.html",
+ "es",
+ "cache/posts/fancy.post.es.html",
+ ),
+ (
+ "{path}.{lang}.{ext}",
+ "cache/pages/charts.html",
+ "es",
+ "cache/pages/charts.es.html",
+ ),
+ (
+ "{path}.{lang}.{ext}",
+ "cache/pages/charts.html",
+ "en",
+ "cache/pages/charts.html",
+ ),
+ ("{path}.{ext}.{lang}", "*.rst", "es", "*.rst.es"),
+ ("{path}.{ext}.{lang}", "*.rst.es", "es", "*.rst.es"),
+ ("{path}.{ext}.{lang}", "*.rst.es", "en", "*.rst"),
+ (
+ "{path}.{ext}.{lang}",
+ "cache/posts/fancy.post.html.es",
+ "en",
+ "cache/posts/fancy.post.html",
+ ),
+ (
+ "{path}.{ext}.{lang}",
+ "cache/posts/fancy.post.html",
+ "es",
+ "cache/posts/fancy.post.html.es",
+ ),
+ ],
+)
+def test_get_translation_candidate(pattern, path, lang, expected_path):
+ config = {
+ "TRANSLATIONS_PATTERN": pattern,
+ "DEFAULT_LANG": "en",
+ "TRANSLATIONS": {"es": "1", "en": 1},
+ }
+ assert get_translation_candidate(config, path, lang) == expected_path
+
+
+def test_TemplateHookRegistry():
+ r = TemplateHookRegistry("foo", None)
+ r.append("Hello!")
+ r.append(lambda x: "Hello " + x + "!", False, "world")
+ assert r() == "Hello!\nHello world!"
+
+
+@pytest.mark.parametrize(
+ "base, expected_path",
+ [
+ ("http://some.site", "/"),
+ ("http://some.site/", "/"),
+ ("http://some.site/some/sub-path", "/some/sub-path/"),
+ ("http://some.site/some/sub-path/", "/some/sub-path/"),
+ ],
+)
+def test_sitemap_get_base_path(base, expected_path):
+ assert expected_path == sitemap_get_base_path(base)
+
+
+@pytest.mark.parametrize(
+ "metadata_format, expected_result",
+ [
+ (
+ "nikola",
+ """\
+.. title: Hello, world!
+.. slug: hello-world
+.. a: 1
+.. b: 2
+
+""",
+ ),
+ (
+ "yaml",
+ """\
+---
+a: '1'
+b: '2'
+slug: hello-world
+title: Hello, world!
+---
+""",
+ ),
+ ],
+)
+def test_write_metadata_with_formats(metadata_format, expected_result):
+ """
+ Test writing metadata with different formats.
+
+ YAML is expected to be sorted alphabetically.
+ Nikola sorts by putting the defaults first and then sorting the rest
+ alphabetically.
+ """
+ data = {"slug": "hello-world", "title": "Hello, world!", "b": "2", "a": "1"}
+ assert write_metadata(data, metadata_format) == expected_result
+
+
+def test_write_metadata_with_format_toml():
+ """
+ Test writing metadata in TOML format.
+
+ TOML is sorted randomly in Python 3.5 or older and by insertion
+ order since Python 3.6.
+ """
+ data = {"slug": "hello-world", "title": "Hello, world!", "b": "2", "a": "1"}
+
+ toml = write_metadata(data, "toml")
+ assert toml.startswith("+++\n")
+ assert toml.endswith("+++\n")
+ assert 'slug = "hello-world"' in toml
+ assert 'title = "Hello, world!"' in toml
+ assert 'b = "2"' in toml
+ assert 'a = "1"' in toml
+
+
+@pytest.mark.parametrize(
+ "wrap, expected_result",
+ [
+ (
+ False,
+ """\
+.. title: Hello, world!
+.. slug: hello-world
+
+""",
+ ),
+ (
+ True,
+ """\
+<!--
+.. title: Hello, world!
+.. slug: hello-world
+-->
+
+""",
+ ),
+ (
+ ("111", "222"),
+ """\
+111
+.. title: Hello, world!
+.. slug: hello-world
+222
+
+""",
+ ),
+ ],
+)
+def test_write_metadata_comment_wrap(wrap, expected_result):
+ data = {"title": "Hello, world!", "slug": "hello-world"}
+ assert write_metadata(data, "nikola", wrap) == expected_result
+
+
+@pytest.mark.parametrize(
+ "metadata_format, expected_results",
+ [
+ (
+ "rest_docinfo",
+ [
+ """=============
+Hello, world!
+=============
+
+:slug: hello-world
+"""
+ ],
+ ),
+ (
+ "markdown_meta",
+ [
+ """title: Hello, world!
+slug: hello-world
+
+""",
+ """slug: hello-world
+title: Hello, world!
+
+""",
+ ],
+ ),
+ ],
+)
+def test_write_metadata_compiler(metadata_format, expected_results):
+ """
+ Test writing metadata with different formats.
+
+ We test for multiple results because some compilers might produce
+ unordered output.
+ """
+ data = {"title": "Hello, world!", "slug": "hello-world"}
+ assert write_metadata(data, metadata_format) in expected_results
+
+
+@pytest.mark.parametrize(
+ "post_format, expected_metadata",
+ [
+ ("rest", "==\nxx\n==\n\n"),
+ ("markdown", "title: xx\n\n"),
+ ("html", ".. title: xx\n\n"),
+ ],
+)
+def test_write_metadata_pelican_detection(post, post_format, expected_metadata):
+ post.name = post_format
+
+ data = {"title": "xx"}
+ assert write_metadata(data, "pelican", compiler=post) == expected_metadata
+
+
+def test_write_metadata_pelican_detection_default():
+ data = {"title": "xx"}
+ assert write_metadata(data, "pelican", compiler=None) == ".. title: xx\n\n"
+
+
+def test_write_metadata_from_site(post):
+ post.config = {"METADATA_FORMAT": "yaml"}
+ data = {"title": "xx"}
+ assert write_metadata(data, site=post) == "---\ntitle: xx\n---\n"
+
+
+def test_write_metadata_default(post):
+ data = {"title": "xx"}
+ assert write_metadata(data) == ".. title: xx\n\n"
+
+
+@pytest.mark.parametrize("arg", ["foo", "filename_regex"])
+def test_write_metadata_fallbacks(post, arg):
+ data = {"title": "xx"}
+ assert write_metadata(data, arg) == ".. title: xx\n\n"
+
+
+@pytest.fixture
+def post():
+ return FakePost()
+
+
+class FakePost:
+ default_lang = "en"
+ metadata_extractors_by = metadata_extractors.default_metadata_extractors_by()
+ config = {
+ "TRANSLATIONS_PATTERN": "{path}.{lang}.{ext}",
+ "TRANSLATIONS": {"en": "./"},
+ "DEFAULT_LANG": "en",
+ }
+
+ def __init__(self):
+ metadata_extractors.load_defaults(self, self.metadata_extractors_by)