From 3a0d66f07b112b6d2bdc2b57bbf717a89a351ce6 Mon Sep 17 00:00:00 2001 From: Unit 193 Date: Wed, 3 Feb 2021 19:17:00 -0500 Subject: New upstream version 8.1.2. --- tests/README.rst | 105 --- tests/__init__.py | 2 +- tests/base.py | 259 ------ tests/conftest.py | 24 +- tests/data/1-nolinks.rst | 1 + .../metadata_extractors/f-html-1-compiler.html | 7 + .../metadata_extractors/f-ipynb-1-compiler.ipynb | 31 + .../metadata_extractors/f-markdown-1-compiler.md | 7 + .../metadata_extractors/f-markdown-1-nikola.md | 13 + .../metadata_extractors/f-markdown-2-nikola.md | 2 + .../metadata_extractors/f-markdown-2-nikola.meta | 7 + .../data/metadata_extractors/f-rest-1-compiler.rst | 9 + tests/data/metadata_extractors/f-rest-1-nikola.rst | 11 + tests/data/metadata_extractors/f-rest-1-toml.rst | 8 + tests/data/metadata_extractors/f-rest-1-yaml.rst | 8 + .../data/metadata_extractors/f-rest-2-nikola.meta | 7 + tests/data/metadata_extractors/f-rest-2-nikola.rst | 2 + tests/data/metadata_extractors/f-rest-2-toml.meta | 6 + tests/data/metadata_extractors/f-rest-2-toml.rst | 2 + tests/data/metadata_extractors/f-rest-2-yaml.meta | 6 + tests/data/metadata_extractors/f-rest-2-yaml.rst | 2 + tests/data/rss-2_0.xsd | 500 +++++++++++ tests/data/test_config/conf.py | 30 + .../config.with+illegal(module)name.characters.py | 6 + tests/data/test_config/prod.py | 6 + tests/data/translated_titles/conf.py | 694 +--------------- tests/data/translated_titles/pages/1.pl.txt | 4 + tests/data/translated_titles/pages/1.txt | 5 + tests/data/translated_titles/stories/1.pl.txt | 4 - tests/data/translated_titles/stories/1.txt | 5 - .../wordpress_import/wordpress_export_example.xml | 322 ++++++++ .../wordpress_qtranslate_item_modernized.xml | 30 + .../wordpress_qtranslate_item_raw_export.xml | 30 + .../wordpress_import/wordpress_unicode_export.xml | 114 +++ tests/helper.py | 131 +++ tests/import_wordpress_and_build_workflow.py | 43 - tests/integration/__init__.py | 12 + tests/integration/conftest.py | 74 ++ tests/integration/helper.py | 56 ++ tests/integration/test_archive_full.py | 44 + tests/integration/test_archive_per_day.py | 36 + tests/integration/test_archive_per_month.py | 36 + tests/integration/test_building_in_subdir.py | 32 + tests/integration/test_category_destpath.py | 88 ++ tests/integration/test_check_absolute_subfolder.py | 50 ++ tests/integration/test_check_failure.py | 47 ++ .../integration/test_check_full_path_subfolder.py | 35 + tests/integration/test_demo_build.py | 43 + tests/integration/test_empty_build.py | 54 ++ tests/integration/test_future_post.py | 109 +++ tests/integration/test_page_index_normal_urls.py | 238 ++++++ tests/integration/test_page_index_pretty_urls.py | 57 ++ tests/integration/test_redirection.py | 106 +++ tests/integration/test_relative_links.py | 60 ++ .../test_relative_links_with_pages_in_root.py | 65 ++ tests/integration/test_repeated_posts_setting.py | 36 + tests/integration/test_translated_content.py | 62 ++ .../test_translated_content_secondary_language.py | 40 + tests/integration/test_translation_patterns.py | 55 ++ tests/integration/test_wordpress_import.py | 59 ++ tests/rss-2_0.xsd | 500 ----------- tests/test_command_import_wordpress.py | 757 +++++++++-------- tests/test_command_import_wordpress_translation.py | 144 ++++ tests/test_command_init.py | 228 ++--- tests/test_commands.py | 15 - tests/test_compile_markdown.py | 158 ++-- tests/test_config.py | 63 ++ tests/test_integration.py | 600 -------------- tests/test_locale.py | 459 +++++------ tests/test_metadata_extractors.py | 225 +++++ tests/test_path_handlers.py | 114 +++ tests/test_plugin_importing.py | 19 - tests/test_plugins.py | 16 + tests/test_rss_feeds.py | 308 +++---- tests/test_rst_compiler.py | 468 +++++------ tests/test_scheduling.py | 255 +++--- tests/test_shortcodes.py | 200 +++++ tests/test_slugify.py | 120 +-- tests/test_task_scale_images.py | 126 +++ tests/test_template_shortcodes.py | 78 ++ tests/test_utils.py | 916 +++++++++++++-------- tests/wordpress_export_example.xml | 322 -------- tests/wordpress_unicode_export.xml | 114 --- 83 files changed, 5714 insertions(+), 4428 deletions(-) delete mode 100644 tests/README.rst delete mode 100644 tests/base.py create mode 100644 tests/data/metadata_extractors/f-html-1-compiler.html create mode 100644 tests/data/metadata_extractors/f-ipynb-1-compiler.ipynb create mode 100644 tests/data/metadata_extractors/f-markdown-1-compiler.md create mode 100644 tests/data/metadata_extractors/f-markdown-1-nikola.md create mode 100644 tests/data/metadata_extractors/f-markdown-2-nikola.md create mode 100644 tests/data/metadata_extractors/f-markdown-2-nikola.meta create mode 100644 tests/data/metadata_extractors/f-rest-1-compiler.rst create mode 100644 tests/data/metadata_extractors/f-rest-1-nikola.rst create mode 100644 tests/data/metadata_extractors/f-rest-1-toml.rst create mode 100644 tests/data/metadata_extractors/f-rest-1-yaml.rst create mode 100644 tests/data/metadata_extractors/f-rest-2-nikola.meta create mode 100644 tests/data/metadata_extractors/f-rest-2-nikola.rst create mode 100644 tests/data/metadata_extractors/f-rest-2-toml.meta create mode 100644 tests/data/metadata_extractors/f-rest-2-toml.rst create mode 100644 tests/data/metadata_extractors/f-rest-2-yaml.meta create mode 100644 tests/data/metadata_extractors/f-rest-2-yaml.rst create mode 100644 tests/data/rss-2_0.xsd create mode 100644 tests/data/test_config/conf.py create mode 100644 tests/data/test_config/config.with+illegal(module)name.characters.py create mode 100644 tests/data/test_config/prod.py create mode 100644 tests/data/translated_titles/pages/1.pl.txt create mode 100644 tests/data/translated_titles/pages/1.txt delete mode 100644 tests/data/translated_titles/stories/1.pl.txt delete mode 100644 tests/data/translated_titles/stories/1.txt create mode 100644 tests/data/wordpress_import/wordpress_export_example.xml create mode 100644 tests/data/wordpress_import/wordpress_qtranslate_item_modernized.xml create mode 100644 tests/data/wordpress_import/wordpress_qtranslate_item_raw_export.xml create mode 100644 tests/data/wordpress_import/wordpress_unicode_export.xml create mode 100644 tests/helper.py delete mode 100644 tests/import_wordpress_and_build_workflow.py create mode 100644 tests/integration/__init__.py create mode 100644 tests/integration/conftest.py create mode 100644 tests/integration/helper.py create mode 100644 tests/integration/test_archive_full.py create mode 100644 tests/integration/test_archive_per_day.py create mode 100644 tests/integration/test_archive_per_month.py create mode 100644 tests/integration/test_building_in_subdir.py create mode 100644 tests/integration/test_category_destpath.py create mode 100644 tests/integration/test_check_absolute_subfolder.py create mode 100644 tests/integration/test_check_failure.py create mode 100644 tests/integration/test_check_full_path_subfolder.py create mode 100644 tests/integration/test_demo_build.py create mode 100644 tests/integration/test_empty_build.py create mode 100644 tests/integration/test_future_post.py create mode 100644 tests/integration/test_page_index_normal_urls.py create mode 100644 tests/integration/test_page_index_pretty_urls.py create mode 100644 tests/integration/test_redirection.py create mode 100644 tests/integration/test_relative_links.py create mode 100644 tests/integration/test_relative_links_with_pages_in_root.py create mode 100644 tests/integration/test_repeated_posts_setting.py create mode 100644 tests/integration/test_translated_content.py create mode 100644 tests/integration/test_translated_content_secondary_language.py create mode 100644 tests/integration/test_translation_patterns.py create mode 100644 tests/integration/test_wordpress_import.py delete mode 100644 tests/rss-2_0.xsd create mode 100644 tests/test_command_import_wordpress_translation.py delete mode 100644 tests/test_commands.py create mode 100644 tests/test_config.py delete mode 100644 tests/test_integration.py create mode 100644 tests/test_metadata_extractors.py create mode 100644 tests/test_path_handlers.py delete mode 100644 tests/test_plugin_importing.py create mode 100644 tests/test_plugins.py create mode 100644 tests/test_shortcodes.py create mode 100644 tests/test_task_scale_images.py create mode 100644 tests/test_template_shortcodes.py delete mode 100644 tests/wordpress_export_example.xml delete mode 100644 tests/wordpress_unicode_export.xml (limited to 'tests') 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 `_. -You can check the `current build status `_ 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 ,." + - "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('') 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 @@ + + + + + +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 @@ + + +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/data/rss-2_0.xsd b/tests/data/rss-2_0.xsd new file mode 100644 index 0000000..d7ddaee --- /dev/null +++ b/tests/data/rss-2_0.xsd @@ -0,0 +1,500 @@ + + + + + XML Schema for RSS v2.0 feed files. + Project home: http://www.codeplex.com/rss2schema/ + Based on the RSS 2.0 specification document at http://cyber.law.harvard.edu/rss/rss.html + Author: Jorgen Thelin + Revision: 16 + Date: 01-Nov-2008 + Feedback to: http://www.codeplex.com/rss2schema/WorkItem/List.aspx + + + + + + + + + + + + + + An item may represent a "story" -- much like a story in a newspaper or magazine; if so its description is a synopsis of the story, and the link points to the full story. An item may also be complete in itself, if so, the description contains the text (entity-encoded HTML is allowed), and the link and title may be omitted. + + + + + + The title of the item. + + + + + The item synopsis. + + + + + The URL of the item. + + + + + Email address of the author of the item. + + + + + Includes the item in one or more categories. + + + + + URL of a page for comments relating to the item. + + + + + Describes a media object that is attached to the item. + + + + + guid or permalink URL for this entry + + + + + Indicates when the item was published. + + + + + The RSS channel that the item came from. + + + + + Extensibility element. + + + + + + + + + + + + The name of the channel. It's how people refer to your service. If you have an HTML website that contains the same information as your RSS file, the title of your channel should be the same as the title of your website. + + + + + The URL to the HTML website corresponding to the channel. + + + + + Phrase or sentence describing the channel. + + + + + The language the channel is written in. This allows aggregators to group all Italian language sites, for example, on a single page. A list of allowable values for this element, as provided by Netscape, is here. You may also use values defined by the W3C. + + + + + Copyright notice for content in the channel. + + + + + Email address for person responsible for editorial content. + + + + + Email address for person responsible for technical issues relating to channel. + + + + + The publication date for the content in the channel. All date-times in RSS conform to the Date and Time Specification of RFC 822, with the exception that the year may be expressed with two characters or four characters (four preferred). + + + + + The last time the content of the channel changed. + + + + + Specify one or more categories that the channel belongs to. + + + + + A string indicating the program used to generate the channel. + + + + + A URL that points to the documentation for the format used in the RSS file. It's probably a pointer to this page. It's for people who might stumble across an RSS file on a Web server 25 years from now and wonder what it is. + + + + + Allows processes to register with a cloud to be notified of updates to the channel, implementing a lightweight publish-subscribe protocol for RSS feeds. + + + + + ttl stands for time to live. It's a number of minutes that indicates how long a channel can be cached before refreshing from the source. + + + + + Specifies a GIF, JPEG or PNG image that can be displayed with the channel. + + + + + The PICS rating for the channel. + + + + + Specifies a text input box that can be displayed with the channel. + + + + + A hint for aggregators telling them which hours they can skip. + + + + + A hint for aggregators telling them which days they can skip. + + + + + Extensibility element. + + + + + + + + + Extensibility element. + + + + + + + + A time in GMT when aggregators should not request the channel data. The hour beginning at midnight is hour zero. + + + + + + + + + + + + + + A day when aggregators should not request the channel data. + + + + + + + + + + + + + + + + A time in GMT, when aggregators should not request the channel data. The hour beginning at midnight is hour zero. + + + + + + + + + + + + + + + + The URL of the image file. + + + + + Describes the image, it's used in the ALT attribute of the HTML <img> tag when the channel is rendered in HTML. + + + + + The URL of the site, when the channel is rendered, the image is a link to the site. (Note, in practice the image <title> and <link> should have the same value as the channel's <title> and <link>. + + + + + The width of the image in pixels. + + + + + The height of the image in pixels. + + + + + Text that is included in the TITLE attribute of the link formed around the image in the HTML rendering. + + + + + + + The height of the image in pixels. + + + + + + + + The width of the image in pixels. + + + + + + + + Specifies a web service that supports the rssCloud interface which can be implemented in HTTP-POST, XML-RPC or SOAP 1.1. Its purpose is to allow processes to register with a cloud to be notified of updates to the channel, implementing a lightweight publish-subscribe protocol for RSS feeds. + + + + + + + + + + + + + + + + + The purpose of this element is something of a mystery! You can use it to specify a search engine box. Or to allow a reader to provide feedback. Most aggregators ignore it. + + + + + The label of the Submit button in the text input area. + + + + + Explains the text input area. + + + + + The name of the text object in the text input area. + + + + + The URL of the CGI script that processes text input requests. + + + + + + + Using the regexp definiton of E-Mail Address by Lucadean from the .NET RegExp Pattern Repository at http://www.3leaf.com/default/NetRegExpRepository.aspx + + + + + + + + A date-time displayed in RFC-822 format. + Using the regexp definiton of rfc-822 date by Sam Ruby at http://www.intertwingly.net/blog/1360.html + + + + + + + + + + + + + + + + + + URL where the enclosure is located + + + + + Size in bytes + + + + + MIME media-type of the enclosure + + + + + + + + + + + + + + + + + + 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: -# -# 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: -# -# 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 = '

{read_more}…

' - -# 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 = """ -# -# Creative Commons License BY-NC-SA""" - -# A small copyright notice for the page footer (in HTML). -# (translatable) CONTENT_FOOTER = 'Contents © {date} {author} - Powered by Nikola {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 .html, put them in -# /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 = """ -# -# """ - -# 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 = """ -# -#
-# Share -#
  • -#
  • -#
  • -#
  • -#
-#
-# -# -# """ - -# 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 = """ -# -# -# -# """ % SITE_URL -# -# If you prefer a google search form, here's an example that should just work: -# SEARCH_FORM = """ -# -# -# -# """ % SITE_URL - -# Also, there is a local search plugin you can use, based on Tipue, but it requires setting several -# options: - -# SEARCH_FORM = """ -# -# -# """ -# -# BODY_END = """ -# -# -# -# """ - -# EXTRA_HEAD_DATA = """ -# -#
-# """ -# 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 -# (translatable) -# EXTRA_HEAD_DATA = "" -# Google Analytics or whatever else you use. Added to the bottom of -# 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\d{4}-\d{2}-\d{2})-(?P.*)-(?P.*)\.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/pages/1.pl.txt b/tests/data/translated_titles/pages/1.pl.txt new file mode 100644 index 0000000..a888c1f --- /dev/null +++ b/tests/data/translated_titles/pages/1.pl.txt @@ -0,0 +1,4 @@ +.. title: Bar +.. slug: 1 + +Bar diff --git a/tests/data/translated_titles/pages/1.txt b/tests/data/translated_titles/pages/1.txt new file mode 100644 index 0000000..45fb214 --- /dev/null +++ b/tests/data/translated_titles/pages/1.txt @@ -0,0 +1,5 @@ +.. title: Foo +.. slug: 1 +.. date: 2001/01/01 00:00:00 + +Foo diff --git a/tests/data/translated_titles/stories/1.pl.txt b/tests/data/translated_titles/stories/1.pl.txt deleted file mode 100644 index a888c1f..0000000 --- a/tests/data/translated_titles/stories/1.pl.txt +++ /dev/null @@ -1,4 +0,0 @@ -.. title: Bar -.. slug: 1 - -Bar diff --git a/tests/data/translated_titles/stories/1.txt b/tests/data/translated_titles/stories/1.txt deleted file mode 100644 index 45fb214..0000000 --- a/tests/data/translated_titles/stories/1.txt +++ /dev/null @@ -1,5 +0,0 @@ -.. title: Foo -.. slug: 1 -.. date: 2001/01/01 00:00:00 - -Foo diff --git a/tests/data/wordpress_import/wordpress_export_example.xml b/tests/data/wordpress_import/wordpress_export_example.xml new file mode 100644 index 0000000..e2401f7 --- /dev/null +++ b/tests/data/wordpress_import/wordpress_export_example.xml @@ -0,0 +1,322 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- This is a WordPress eXtended RSS file generated by WordPress as an export of your site. --> +<rss version="2.0" + xmlns:excerpt="http://wordpress.org/export/1.2/excerpt/" + xmlns:content="http://purl.org/rss/1.0/modules/content/" + xmlns:wfw="http://wellformedweb.org/CommentAPI/" + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:wp="http://wordpress.org/export/1.2/" +> + +<channel> + <title>Wordpress blog title + http://some.blog + Nikola test blog ;) - with moré Ümläüts + Wed, 25 Jul 2012 22:31:24 +0000 + de-DE + 1.2 + http://some.blog + http://some.blog + + 2Nikomail@some.blog + + 11programmierung + 501dotnet + + http://wordpress.org/?v=3.4.1 + + + Arzt+Pfusch - S.I.C.K. + http://some.blog/2008/07/arzt-und-pfusch-s-i-c-k/arzt_und_pfusch-sick-cover/ + Thu, 16 Jul 2009 19:40:37 +0000 + Niko + http://some.blog/wp-content/uploads/2008/07/arzt_und_pfusch-sick-cover.png + + + + 10 + 2009-07-16 21:40:37 + 2009-07-16 19:40:37 + open + open + arzt_und_pfusch-sick-cover + inherit + 6 + 0 + attachment + + 0 + http://some.blog/wp-content/uploads/2008/07/arzt_und_pfusch-sick-cover.png + + _wp_attached_file + + + + _wp_attachment_metadata + + + + + + Transformation test + http://some.blog/2007/04/hoert/ + Fri, 27 Apr 2007 13:02:35 +0000 + Niko + http://some.blog/?p=17 + + [/caption] + +Some source code. + +[sourcecode language="Python"] +import sys +print sys.version +[/sourcecode] + +The end. +]]> + + 17 + 2007-04-27 15:02:35 + 2007-04-27 13:02:35 + open + open + hoert + publish + 0 + 0 + post + + 0 + + + + + + _edit_last + + + + + + Arzt+Pfusch - S.I.C.K. + http://some.blog/2008/07/arzt-und-pfusch-s-i-c-k/ + Sat, 12 Jul 2008 19:22:06 +0000 + Niko + http://some.blog/?p=6 + + Arzt+Pfusch - S.I.C.K.Gerade bin ich über das Album S.I.C.K von Arzt+Pfusch gestolpert, welches Arzt+Pfusch zum Download für lau anbieten. Das Album steht unter einer Creative Commons BY-NC-ND-Lizenz. +Die Ladung noisebmstupidevildustrial gibts als MP3s mit 64kbps und VBR, als Ogg Vorbis und als FLAC (letztere hier). Artwork und Lyrics gibts nochmal einzeln zum Download.]]> + + 6 + 2008-07-12 21:22:06 + 2008-07-12 19:22:06 + open + open + arzt-und-pfusch-s-i-c-k + publish + 0 + 0 + post + + 0 + + + + + + + + + + + + _edit_last + + + + + + Kontakt + http://some.blog/kontakt/ + Thu, 16 Jul 2009 18:20:32 +0000 + Niko + http://some.blog/?page_id=3 + + Datenschutz +Ich erhebe und speichere automatisch in meine Server Log Files Informationen, die dein Browser an mich übermittelt. Dies sind: +
    +
  • Browsertyp und -version
  • +
  • verwendetes Betriebssystem
  • +
  • Referrer URL (die zuvor besuchte Seite)
  • +
  • IP Adresse des zugreifenden Rechners
  • +
  • Uhrzeit der Serveranfrage.
  • +
+Diese Daten sind für mich nicht bestimmten Personen zuordenbar. Eine Zusammenführung dieser Daten mit anderen Datenquellen wird nicht vorgenommen, die Daten werden einzig zu statistischen Zwecken erhoben.]]>
+ + 3 + 2009-07-16 20:20:32 + 2009-07-16 18:20:32 + closed + closed + kontakt + publish + 0 + 0 + page + + 0 + + _edit_last + + + + _wp_page_template + + +
+ + Indentation Test + http://some.blog/2012/04/indentation_test/ + Sun, 15 Apr 2012 11:44:59 +0000 + Niko + http://some.blog/?p=2077 + + class Borg: + _state = {} + def __init__(self): + self.__dict__ = self._state +  + +Here is a listing made with HTML that should display without the HTML being visible to the visitor. +
    +
  • to post: groupname@googlegroups.com
  • +
  • to subscribe: groupname+subscribe@googlegroups.com
  • +
  • to unsubscribe: groupname+unsubscribe@googlegroups.com
  • +
+ +A listing with another listing inside. +
    +
  • foo +
      +
    • bar +
    +
+]]>
+ + 2077 + 2012-04-15 12:44:59 + 2012-04-15 11:44:59 + open + open + python-borg-pattern + publish + 0 + 0 + post + + 0 + + + + + _edit_last + + +
+ + + Screenshot - 2012-12-19 + http://some.blog/2012/12/wintermodus/2012-12-19-1355925145_1024x600_scrot/ + Wed, 19 Dec 2012 13:53:19 +0000 + Niko + http://some.blog/wp-content/uploads/2012/12/2012-12-19-355925145_1024x600_scrot.png + + + + 2271 + 2012-12-19 14:53:19 + 2012-12-19 13:53:19 + open + open + 2012-12-19-1355925145_1024x600_scrot + inherit + 2270 + 0 + attachment + + 0 + http://some.blog/wp-content/uploads/2012/12/2012-12-19-355925145_1024x600_scrot.png + + _wp_attached_file + + + + _wp_attachment_metadata + + + + + + NoirsEtPleinsDeLumière + http://some.blog/2011/04/noirs-et-pourtant-pleins-de-lumiere/noirsetpleinsdelumiere/#main + Tue, 12 Apr 2011 21:56:05 +0000 + + http://some.blog/wp-content/uploads/2011/04/NoirsEtPleinsDeLumière.jpg + + + + 724 + 2011-04-12 23:56:05 + 2011-04-12 21:56:05 + open + closed + noirsetpleinsdelumiere + inherit + 723 + 0 + attachment + + 0 + http://some.blog/wp-content/uploads/2011/04/NoirsEtPleinsDeLumière.jpg + + _wp_attachment_metadata + + + + _wp_attached_file + + + + + Image Link Rewriting + http://some.blog/2012/12/wintermodus/ + Wed, 19 Dec 2012 13:55:10 +0000 + Niko + http://some.blog/?p=2270 + + Netbook Screenshot - 2012-12-19]]> + + 2270 + 2012-12-19 14:55:10 + 2012-12-19 13:55:10 + open + open + image-link-rewriting + publish + 0 + 0 + post + + 0 + + + + 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 @@ + + [:fr]Sous le ciel[:][:en]Under heaven[:] + http://www.tibonihoo.net/blog/2014/05/sous-le-ciel/ + Sat, 03 May 2014 13:20:32 +0000 + + http://www.tibonihoo.net/blog/?p=1585 + + IMG_6851[:][:en]IMG_6851[:]]]> + + 1585 + + + + + + + 0 + 0 + + + 0 + + + + + + + + + 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 @@ + + <!--:fr-->Sous le ciel<!--:--><!--:en-->Under heaven<!--:--> + http://www.tibonihoo.net/blog/2014/05/sous-le-ciel/ + Sat, 03 May 2014 13:20:32 +0000 + + http://www.tibonihoo.net/blog/?p=1585 + + IMG_6851IMG_6851]]> + + 1585 + + + + + + + 0 + 0 + + + 0 + + + + + + + + + diff --git a/tests/data/wordpress_import/wordpress_unicode_export.xml b/tests/data/wordpress_import/wordpress_unicode_export.xml new file mode 100644 index 0000000..b2204fc --- /dev/null +++ b/tests/data/wordpress_import/wordpress_unicode_export.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + Nikola Unicode Test ͵pó®t + http://nikolaunicode.wordpress.com + The greatest WordPress.com site in all the land! + Tue, 25 Dec 2012 21:39:30 +0000 + en + 1.2 + http://wordpress.com/ + http://nikolaunicode.wordpress.com + + 3804924ralsinaroberto.alsina@gmail.com + + 1uncategorized + 132937998thag1 + 132937999thag%c2%b2 + + http://wordpress.com/ + + + https://s2.wp.com/i/buttonw-com.png + Nikola Unicode Test ͵pó®t + http://nikolaunicode.wordpress.com + + + 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 '' + + # 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 "https://example.com/foo/" 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 "https://example.com/" 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 = '' + 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 = '' + 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 = '' + 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 "https://example.com/" not in sitemap_data + assert "https://example.com/foo/bar/" 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 "https://example.com/" not in sitemap_data + assert "https://example.com/blog/index.html" 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/rss-2_0.xsd b/tests/rss-2_0.xsd deleted file mode 100644 index d7ddaee..0000000 --- a/tests/rss-2_0.xsd +++ /dev/null @@ -1,500 +0,0 @@ - - - - - XML Schema for RSS v2.0 feed files. - Project home: http://www.codeplex.com/rss2schema/ - Based on the RSS 2.0 specification document at http://cyber.law.harvard.edu/rss/rss.html - Author: Jorgen Thelin - Revision: 16 - Date: 01-Nov-2008 - Feedback to: http://www.codeplex.com/rss2schema/WorkItem/List.aspx - - - - - - - - - - - - - - An item may represent a "story" -- much like a story in a newspaper or magazine; if so its description is a synopsis of the story, and the link points to the full story. An item may also be complete in itself, if so, the description contains the text (entity-encoded HTML is allowed), and the link and title may be omitted. - - - - - - The title of the item. - - - - - The item synopsis. - - - - - The URL of the item. - - - - - Email address of the author of the item. - - - - - Includes the item in one or more categories. - - - - - URL of a page for comments relating to the item. - - - - - Describes a media object that is attached to the item. - - - - - guid or permalink URL for this entry - - - - - Indicates when the item was published. - - - - - The RSS channel that the item came from. - - - - - Extensibility element. - - - - - - - - - - - - The name of the channel. It's how people refer to your service. If you have an HTML website that contains the same information as your RSS file, the title of your channel should be the same as the title of your website. - - - - - The URL to the HTML website corresponding to the channel. - - - - - Phrase or sentence describing the channel. - - - - - The language the channel is written in. This allows aggregators to group all Italian language sites, for example, on a single page. A list of allowable values for this element, as provided by Netscape, is here. You may also use values defined by the W3C. - - - - - Copyright notice for content in the channel. - - - - - Email address for person responsible for editorial content. - - - - - Email address for person responsible for technical issues relating to channel. - - - - - The publication date for the content in the channel. All date-times in RSS conform to the Date and Time Specification of RFC 822, with the exception that the year may be expressed with two characters or four characters (four preferred). - - - - - The last time the content of the channel changed. - - - - - Specify one or more categories that the channel belongs to. - - - - - A string indicating the program used to generate the channel. - - - - - A URL that points to the documentation for the format used in the RSS file. It's probably a pointer to this page. It's for people who might stumble across an RSS file on a Web server 25 years from now and wonder what it is. - - - - - Allows processes to register with a cloud to be notified of updates to the channel, implementing a lightweight publish-subscribe protocol for RSS feeds. - - - - - ttl stands for time to live. It's a number of minutes that indicates how long a channel can be cached before refreshing from the source. - - - - - Specifies a GIF, JPEG or PNG image that can be displayed with the channel. - - - - - The PICS rating for the channel. - - - - - Specifies a text input box that can be displayed with the channel. - - - - - A hint for aggregators telling them which hours they can skip. - - - - - A hint for aggregators telling them which days they can skip. - - - - - Extensibility element. - - - - - - - - - Extensibility element. - - - - - - - - A time in GMT when aggregators should not request the channel data. The hour beginning at midnight is hour zero. - - - - - - - - - - - - - - A day when aggregators should not request the channel data. - - - - - - - - - - - - - - - - A time in GMT, when aggregators should not request the channel data. The hour beginning at midnight is hour zero. - - - - - - - - - - - - - - - - The URL of the image file. - - - - - Describes the image, it's used in the ALT attribute of the HTML <img> tag when the channel is rendered in HTML. - - - - - The URL of the site, when the channel is rendered, the image is a link to the site. (Note, in practice the image <title> and <link> should have the same value as the channel's <title> and <link>. - - - - - The width of the image in pixels. - - - - - The height of the image in pixels. - - - - - Text that is included in the TITLE attribute of the link formed around the image in the HTML rendering. - - - - - - - The height of the image in pixels. - - - - - - - - The width of the image in pixels. - - - - - - - - Specifies a web service that supports the rssCloud interface which can be implemented in HTTP-POST, XML-RPC or SOAP 1.1. Its purpose is to allow processes to register with a cloud to be notified of updates to the channel, implementing a lightweight publish-subscribe protocol for RSS feeds. - - - - - - - - - - - - - - - - - The purpose of this element is something of a mystery! You can use it to specify a search engine box. Or to allow a reader to provide feedback. Most aggregators ignore it. - - - - - The label of the Submit button in the text input area. - - - - - Explains the text input area. - - - - - The name of the text object in the text input area. - - - - - The URL of the CGI script that processes text input requests. - - - - - - - Using the regexp definiton of E-Mail Address by Lucadean from the .NET RegExp Pattern Repository at http://www.3leaf.com/default/NetRegExpRepository.aspx - - - - - - - - A date-time displayed in RFC-822 format. - Using the regexp definiton of rfc-822 date by Sam Ruby at http://www.intertwingly.net/blog/1360.html - - - - - - - - - - - - - - - - - - URL where the enclosure is located - - - - - Size in bytes - - - - - MIME media-type of the enclosure - - - - - - - - - - - - - - - - - - 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'À propos. - -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 = """Si vous préférez savoir à qui vous parlez commencez par visiter l'À propos. - -Quoiqu'il en soit, commentaires, questions et suggestions sont les bienvenues ! -If you'd like to know who you're talking to, please visit the about page. - -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'À propos. - -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 about page. - -Comments, questions and suggestions are welcome ! -""", content_translations["en"]) - - def test_split_a_two_language_post_with_teaser(self): - content = """Si vous préférez savoir à qui vous parlez commencez par visiter l'À propos. - -Quoiqu'il en soit, commentaires, questions et suggestions sont les bienvenues ! -If you'd like to know who you're talking to, please visit the about page. - -Comments, questions and suggestions are welcome ! - -Plus de détails ici ! - -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'À propos. - -Quoiqu'il en soit, commentaires, questions et suggestions sont les bienvenues ! - \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 about page. - -Comments, questions and suggestions are welcome ! - \n\ -More details here ! -""", content_translations["en"]) - - def test_split_a_two_language_post_with_intermission(self): - content = """Voila voilaCOMMONBLA""" - 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 = """Voila voilaCOMMONMOUFBLA""" - 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 = """Voila voilaBLACOMMONMOUF""" - 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. caption test @@ -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), - '''Arzt+Pfusch - S.I.C.K.Arzt+Pfusch - S.I.C.K.Gerade bin ich \xfcber das Album S.I.C.K von Arzt+Pfusch gestolpert, welches Arzt+Pfusch zum Download f\xfcr lau anbieten. Das Album steht unter einer Creative Commons BY-NC-ND-Lizenz. - -Die Ladung noisebmstupidevildustrial gibts als MP3s mit 64kbps und VBR, als Ogg Vorbis und als FLAC (letztere hier). Artwork und Lyrics gibts nochmal einzeln zum Download.''', True) - write_content.assert_any_call( - 'new_site/stories/kontakt.md'.replace('/', os.sep), """

Datenschutz

- +""", + 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), + """Arzt+Pfusch - S.I.C.K.Arzt+Pfusch - S.I.C.K.Gerade bin ich \xfcber das Album S.I.C.K von Arzt+Pfusch gestolpert, welches Arzt+Pfusch zum Download f\xfcr lau anbieten. Das Album steht unter einer Creative Commons BY-NC-ND-Lizenz. +Die Ladung noisebmstupidevildustrial gibts als MP3s mit 64kbps und VBR, als Ogg Vorbis und als FLAC (letztere hier). Artwork und Lyrics gibts nochmal einzeln zum Download.""", + True, + ) + write_content.assert_any_call( + "new_site/pages/kontakt.md".replace("/", os.sep), + """

Datenschutz

Ich erhebe und speichere automatisch in meine Server Log Files Informationen, die dein Browser an mich \xfcbermittelt. Dies sind: -
    -
  • Browsertyp und -version
  • -
  • verwendetes Betriebssystem
  • -
  • Referrer URL (die zuvor besuchte Seite)
  • -
  • IP Adresse des zugreifenden Rechners
  • -
  • Uhrzeit der Serveranfrage.
  • -
- -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"]beautiful picture[/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"]beautiful picture[/caption]' + transformed_content = import_command.transform_caption(caption) - expected_content = 'beautiful picture' + expected_content = 'beautiful picture' - 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"]beautiful picture[/caption] asdasdas asdasdas [caption id="attachment_16" align="alignnone" width="739" caption="beautiful picture"]beautiful picture[/caption] asdasdas""" - expected_content = """asdasdas + expected_content = """asdasdas beautiful picture asdasdas asdasdas beautiful picture 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"]Fehlermeldung[/caption]""" - transformed_content = self.import_command.transform_caption(content) - expected_content = """Fehlermeldung""" - self.assertEqual(expected_content, transformed_content) +def test_transform_caption_with_link_inside(import_command): + content = """[caption caption="Fehlermeldung"]Fehlermeldung[/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 = """Fehlermeldung""" + 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'

FOO

'), - mock.call().__exit__(None, None, None)] - ) + mock.call().write(b"

FOO

"), + 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( + "Voila voilaCOMMONBLA", + "Voila voila COMMON", + "COMMON BLA", + id="withintermission", + ), + pytest.param( + "Voila voilaCOMMONMOUFBLA", + "Voila voila COMMON MOUF", + "COMMON BLA", + id="with uneven repartition", + ), + pytest.param( + "Voila voilaBLACOMMONMOUF", + "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'À propos. + +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"Voila voilaCOMMONMOUFBLA" + output = modernize_qtranslate_tags(content) + assert b"[:fr]Voila voila[:]COMMON[:fr]MOUF[:][:en]BLA[:]" == output + + +def test_split_a_two_language_post(): + content = """Si vous préférez savoir à qui vous parlez commencez par visiter l'À propos. + +Quoiqu'il en soit, commentaires, questions et suggestions sont les bienvenues ! +If you'd like to know who you're talking to, please visit the about page. + +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'À propos. + +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 about page. + +Comments, questions and suggestions are welcome ! +""" + ) + + +def test_split_a_two_language_post_with_teaser(): + content = """Si vous préférez savoir à qui vous parlez commencez par visiter l'À propos. + +Quoiqu'il en soit, commentaires, questions et suggestions sont les bienvenues ! +If you'd like to know who you're talking to, please visit the about page. + +Comments, questions and suggestions are welcome ! + +Plus de détails ici ! + +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'À propos. + +Quoiqu'il en soit, commentaires, questions et suggestions sont les bienvenues ! + \n\ +Plus de détails ici ! +""" + ) + assert ( + content_translations["en"] == """If you'd like to know who you're talking to, please visit the about page. + +Comments, questions and suggestions are welcome ! + \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]", + '

', + id="mdx podcast", + ), + pytest.param( + "~~striked out text~~", + "

striked out text

", + id="strikethrough", + ), + pytest.param( + """\ + #!python + from this +""", + """\ +
\ +
1
\ +
\
+from this
+
+
+""", + 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 = '''\ -
\ -
1
\ -
\
-from this
-
-
-''' - - 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 = '''\ -

Here's a gist file inline: -

- - -
-

-

Cool, eh?

-''' - 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 = '''\ -

Here's a gist file inline, using reStructuredText syntax: -

- - -
-

-

Cool, eh?

-''' - 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('https://example.com/index.html' 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('https://example.com/' in sitemap_data) - self.assertTrue('https://example.com/foo/bar/index.html' 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('https://example.com/foo/index.html' 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('https://example.com/' in sitemap_data) - self.assertTrue('https://example.com/blog/index.html' 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 ' - - 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 ' + 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 `" + 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 `' - - 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"?#Hl/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"?#Hl/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\d{4}-\d{2}-\d{2})-(?P.*)-(?P.*)\.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) diff --git a/tests/wordpress_export_example.xml b/tests/wordpress_export_example.xml deleted file mode 100644 index e2401f7..0000000 --- a/tests/wordpress_export_example.xml +++ /dev/null @@ -1,322 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" ?> -<!-- This is a WordPress eXtended RSS file generated by WordPress as an export of your site. --> -<rss version="2.0" - xmlns:excerpt="http://wordpress.org/export/1.2/excerpt/" - xmlns:content="http://purl.org/rss/1.0/modules/content/" - xmlns:wfw="http://wellformedweb.org/CommentAPI/" - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:wp="http://wordpress.org/export/1.2/" -> - -<channel> - <title>Wordpress blog title - http://some.blog - Nikola test blog ;) - with moré Ümläüts - Wed, 25 Jul 2012 22:31:24 +0000 - de-DE - 1.2 - http://some.blog - http://some.blog - - 2Nikomail@some.blog - - 11programmierung - 501dotnet - - http://wordpress.org/?v=3.4.1 - - - Arzt+Pfusch - S.I.C.K. - http://some.blog/2008/07/arzt-und-pfusch-s-i-c-k/arzt_und_pfusch-sick-cover/ - Thu, 16 Jul 2009 19:40:37 +0000 - Niko - http://some.blog/wp-content/uploads/2008/07/arzt_und_pfusch-sick-cover.png - - - - 10 - 2009-07-16 21:40:37 - 2009-07-16 19:40:37 - open - open - arzt_und_pfusch-sick-cover - inherit - 6 - 0 - attachment - - 0 - http://some.blog/wp-content/uploads/2008/07/arzt_und_pfusch-sick-cover.png - - _wp_attached_file - - - - _wp_attachment_metadata - - - - - - Transformation test - http://some.blog/2007/04/hoert/ - Fri, 27 Apr 2007 13:02:35 +0000 - Niko - http://some.blog/?p=17 - - [/caption] - -Some source code. - -[sourcecode language="Python"] -import sys -print sys.version -[/sourcecode] - -The end. -]]> - - 17 - 2007-04-27 15:02:35 - 2007-04-27 13:02:35 - open - open - hoert - publish - 0 - 0 - post - - 0 - - - - - - _edit_last - - - - - - Arzt+Pfusch - S.I.C.K. - http://some.blog/2008/07/arzt-und-pfusch-s-i-c-k/ - Sat, 12 Jul 2008 19:22:06 +0000 - Niko - http://some.blog/?p=6 - - Arzt+Pfusch - S.I.C.K.Gerade bin ich über das Album S.I.C.K von Arzt+Pfusch gestolpert, welches Arzt+Pfusch zum Download für lau anbieten. Das Album steht unter einer Creative Commons BY-NC-ND-Lizenz. -Die Ladung noisebmstupidevildustrial gibts als MP3s mit 64kbps und VBR, als Ogg Vorbis und als FLAC (letztere hier). Artwork und Lyrics gibts nochmal einzeln zum Download.]]> - - 6 - 2008-07-12 21:22:06 - 2008-07-12 19:22:06 - open - open - arzt-und-pfusch-s-i-c-k - publish - 0 - 0 - post - - 0 - - - - - - - - - - - - _edit_last - - - - - - Kontakt - http://some.blog/kontakt/ - Thu, 16 Jul 2009 18:20:32 +0000 - Niko - http://some.blog/?page_id=3 - - Datenschutz -Ich erhebe und speichere automatisch in meine Server Log Files Informationen, die dein Browser an mich übermittelt. Dies sind: -
    -
  • Browsertyp und -version
  • -
  • verwendetes Betriebssystem
  • -
  • Referrer URL (die zuvor besuchte Seite)
  • -
  • IP Adresse des zugreifenden Rechners
  • -
  • Uhrzeit der Serveranfrage.
  • -
-Diese Daten sind für mich nicht bestimmten Personen zuordenbar. Eine Zusammenführung dieser Daten mit anderen Datenquellen wird nicht vorgenommen, die Daten werden einzig zu statistischen Zwecken erhoben.]]>
- - 3 - 2009-07-16 20:20:32 - 2009-07-16 18:20:32 - closed - closed - kontakt - publish - 0 - 0 - page - - 0 - - _edit_last - - - - _wp_page_template - - -
- - Indentation Test - http://some.blog/2012/04/indentation_test/ - Sun, 15 Apr 2012 11:44:59 +0000 - Niko - http://some.blog/?p=2077 - - class Borg: - _state = {} - def __init__(self): - self.__dict__ = self._state -  - -Here is a listing made with HTML that should display without the HTML being visible to the visitor. -
    -
  • to post: groupname@googlegroups.com
  • -
  • to subscribe: groupname+subscribe@googlegroups.com
  • -
  • to unsubscribe: groupname+unsubscribe@googlegroups.com
  • -
- -A listing with another listing inside. -
    -
  • foo -
      -
    • bar -
    -
-]]>
- - 2077 - 2012-04-15 12:44:59 - 2012-04-15 11:44:59 - open - open - python-borg-pattern - publish - 0 - 0 - post - - 0 - - - - - _edit_last - - -
- - - Screenshot - 2012-12-19 - http://some.blog/2012/12/wintermodus/2012-12-19-1355925145_1024x600_scrot/ - Wed, 19 Dec 2012 13:53:19 +0000 - Niko - http://some.blog/wp-content/uploads/2012/12/2012-12-19-355925145_1024x600_scrot.png - - - - 2271 - 2012-12-19 14:53:19 - 2012-12-19 13:53:19 - open - open - 2012-12-19-1355925145_1024x600_scrot - inherit - 2270 - 0 - attachment - - 0 - http://some.blog/wp-content/uploads/2012/12/2012-12-19-355925145_1024x600_scrot.png - - _wp_attached_file - - - - _wp_attachment_metadata - - - - - - NoirsEtPleinsDeLumière - http://some.blog/2011/04/noirs-et-pourtant-pleins-de-lumiere/noirsetpleinsdelumiere/#main - Tue, 12 Apr 2011 21:56:05 +0000 - - http://some.blog/wp-content/uploads/2011/04/NoirsEtPleinsDeLumière.jpg - - - - 724 - 2011-04-12 23:56:05 - 2011-04-12 21:56:05 - open - closed - noirsetpleinsdelumiere - inherit - 723 - 0 - attachment - - 0 - http://some.blog/wp-content/uploads/2011/04/NoirsEtPleinsDeLumière.jpg - - _wp_attachment_metadata - - - - _wp_attached_file - - - - - Image Link Rewriting - http://some.blog/2012/12/wintermodus/ - Wed, 19 Dec 2012 13:55:10 +0000 - Niko - http://some.blog/?p=2270 - - Netbook Screenshot - 2012-12-19]]> - - 2270 - 2012-12-19 14:55:10 - 2012-12-19 13:55:10 - open - open - image-link-rewriting - publish - 0 - 0 - post - - 0 - - - - diff --git a/tests/wordpress_unicode_export.xml b/tests/wordpress_unicode_export.xml deleted file mode 100644 index b2204fc..0000000 --- a/tests/wordpress_unicode_export.xml +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - Nikola Unicode Test ͵pó®t - http://nikolaunicode.wordpress.com - The greatest WordPress.com site in all the land! - Tue, 25 Dec 2012 21:39:30 +0000 - en - 1.2 - http://wordpress.com/ - http://nikolaunicode.wordpress.com - - 3804924ralsinaroberto.alsina@gmail.com - - 1uncategorized - 132937998thag1 - 132937999thag%c2%b2 - - http://wordpress.com/ - - - https://s2.wp.com/i/buttonw-com.png - Nikola Unicode Test ͵pó®t - http://nikolaunicode.wordpress.com - - - -- cgit v1.2.3