aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorLibravatarAgustin Henze <tin@sluc.org.ar>2014-06-13 21:51:04 -0300
committerLibravatarAgustin Henze <tin@sluc.org.ar>2014-06-13 21:51:04 -0300
commit3dddbd8cc879402c2047919bccd20e6697082657 (patch)
tree38d6290f37be1d67d91c46027974e6ee3372e232 /tests
parent7ac2cf148f7a8ea0de126fed3360b49964ce9b45 (diff)
parent58c4878526dec5510f23c812274686787d8724ba (diff)
Merge tag 'upstream/7.0.1'
Upstream version 7.0.1
Diffstat (limited to 'tests')
-rw-r--r--tests/README.rst5
-rw-r--r--tests/__init__.py25
-rw-r--r--tests/base.py79
-rw-r--r--tests/conftest.py8
-rw-r--r--tests/data/translated_titles/conf.py578
-rw-r--r--tests/data/translated_titles/stories/1.pl.txt (renamed from tests/data/translated_titles/stories/1.txt.pl)0
-rw-r--r--tests/test_command_import_wordpress.py1
-rw-r--r--tests/test_command_init.py57
-rw-r--r--tests/test_commands.py18
-rw-r--r--tests/test_compile_markdown.py16
-rw-r--r--tests/test_integration.py82
-rw-r--r--tests/test_rss_feeds.py10
-rw-r--r--tests/test_rst_compiler.py93
-rw-r--r--tests/test_scheduling.py145
-rw-r--r--tests/test_utils.py90
15 files changed, 863 insertions, 344 deletions
diff --git a/tests/README.rst b/tests/README.rst
index 8b2199a..cdf5c8a 100644
--- a/tests/README.rst
+++ b/tests/README.rst
@@ -70,10 +70,9 @@ How to execute the tests
The command to execute tests is::
- nosetests --with-coverage --cover-package=nikola --with-doctest --doctest-options=+NORMALIZE_WHITESPACE --logging-filter=-yapsy
+ doit coverage
-However, this command may change at any given moment. Check the
-``/.travis.yml`` file to get the current command.
+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.
diff --git a/tests/__init__.py b/tests/__init__.py
index 6ad8bac..e69de29 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright © 2012-2014 Roberto Alsina and others.
-
-# Permission is hereby granted, free of charge, to any
-# person obtaining a copy of this software and associated
-# documentation files (the "Software"), to deal in the
-# Software without restriction, including without limitation
-# the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the
-# Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice
-# shall be included in all copies or substantial portions of
-# the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
-# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
-# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
-# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/tests/base.py b/tests/base.py
index f3e3545..c22fbb2 100644
--- a/tests/base.py
+++ b/tests/base.py
@@ -22,11 +22,21 @@ import unittest
import logbook
-# Make logbook shutup
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,
+ RestExtension,
+ MarkdownExtension
+)
+
if sys.version_info < (2, 7):
@@ -167,3 +177,68 @@ class LocaleSupportInTesting(object):
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}',
+ }
+ 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,
+ "RestExtension": RestExtension,
+ "MarkdownExtension": MarkdownExtension,
+ })
+ self.loghandlers = [nikola.utils.STDERR_HANDLER]
+ 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.timeline = [
+ FakePost(title='Fake post',
+ slug='fake-post')
+ ]
+ self.debug = True
+ # This is to make plugin initialization happy
+ self.template_system = self
+ self.name = 'mako'
+
+ def render_template(self, name, _, context):
+ return('<img src="IMG.jpg">')
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..fbb09c8
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,8 @@
+import os
+import pytest
+
+@pytest.yield_fixture(autouse=True)
+def ensure_chdir():
+ x = os.getcwd()
+ yield
+ os.chdir(x)
diff --git a/tests/data/translated_titles/conf.py b/tests/data/translated_titles/conf.py
index edfce3e..4904586 100644
--- a/tests/data/translated_titles/conf.py
+++ b/tests/data/translated_titles/conf.py
@@ -1,40 +1,68 @@
# -*- coding: utf-8 -*-
+
from __future__ import unicode_literals
import time
-##############################################
-# Configuration, please edit
-##############################################
+# !! 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"
-BLOG_TITLE = "Demo 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 = "http://nikola.ralsina.com.ar"
+SITE_URL = "http://getnikola.com/"
# This is the URL where nikola's output will be deployed.
# If not set, defaults to SITE_URL
-# BASE_URL = "http://nikola.ralsina.com.ar
+# BASE_URL = "http://getnikola.com/"
BLOG_EMAIL = "joe@demo.site"
-BLOG_DESCRIPTION = "This is a demo site for Nikola."
+BLOG_DESCRIPTION = "This is a demo site for Nikola." # (translatable)
# Nikola is multilingual!
#
# Currently supported languages are:
-# English -> en
-# Greek -> gr
-# German -> de
-# French -> fr
-# Polish -> pl
-# Russian -> ru
-# Spanish -> es
-# Italian -> it
-# Simplified Chinese -> zh-cn
+# 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
-# (p.e. look at the modules at: ./nikola/data/themes/default/messages/fr.py).
+# (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.
@@ -46,47 +74,53 @@ DEFAULT_LANG = "en"
# the path will be used as a prefix for the generated pages location
TRANSLATIONS = {
"en": "",
- # Example for another language:
"pl": "./pl",
}
# What will translated input files be named like?
-# If you have a page something.rst, then something.rst.pl will be considered
+# If you have a page something.rst, then something.pl.rst will be considered
# its Polish translation.
-# (in the above example: path == "something", lang == "pl", ext == "rst")
+# (in the above example: path == "something", ext == "rst", lang == "pl")
# this pattern is also used for metadata:
-# something.meta -> something.meta.pl
+# something.meta -> something.pl.meta
-TRANSLATIONS_PATTERN = "{path}.{ext}.{lang}"
-
-# If you don't want your Polish files to be considered Perl code, use this:
-# TRANSLATIONS_PATTERN = "{path}.{lang}.{ext}"
-# Note that this pattern will become the default in v7.0.0.
+TRANSLATIONS_PATTERN = "{path}.{lang}.{ext}"
# Links for the sidebar / navigation bar.
# You should provide a key-value pair for each used language.
-SIDEBAR_LINKS = {
+# (the same way you would do with a (translatable) setting.)
+NAVIGATION_LINKS = {
DEFAULT_LANG: (
('/archive.html', 'Archives'),
('/categories/index.html', 'Tags'),
+ ('/rss.xml', 'RSS'),
),
- "pl": ()
}
-
-##############################################
# Below this point, everything is optional
-##############################################
-
-# post_pages contains (wildcard, destination, template, use_in_feed) tuples.
+# 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 must have an associated metadata file (whatever/thing.meta),
+#
+# That fragment could have an associated metadata file (whatever/thing.meta),
# and optionally translated files (example for spanish, with code "es"):
-# whatever/thing.txt.es and whatever/thing.meta.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)
@@ -95,15 +129,20 @@ SIDEBAR_LINKS = {
# pages, which will be placed at
# output / TRANSLATIONS[lang] / destination / pagename.html
#
-# where "pagename" is specified in the metadata file.
+# where "pagename" is the "slug" specified in the metadata file.
#
-# if use_in_feed is True, then those posts will be added to the site's
-# rss feeds.
+# 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.
#
-post_pages = (
- ("posts/*.txt", "posts", "post.tmpl", True),
- ("stories/*.txt", "stories", "story.tmpl", False),
+POSTS = (
+ ("posts/*.rst", "posts", "post.tmpl"),
+ ("posts/*.txt", "posts", "post.tmpl"),
+)
+PAGES = (
+ ("stories/*.rst", "stories", "story.tmpl"),
+ ("stories/*.txt", "stories", "story.tmpl"),
)
# One or more folders containing files to be copied as-is into the output.
@@ -120,20 +159,35 @@ post_pages = (
# 'markdown' is MarkDown
# 'html' assumes the file is html and just copies it
COMPILERS = {
- "rest": ('.txt', '.rst'),
+ "rest": ('.rst', '.txt'),
"markdown": ('.md', '.mdown', '.markdown'),
"textile": ('.textile',),
"txt2tags": ('.t2t',),
"bbcode": ('.bb',),
"wiki": ('.wiki',),
"ipynb": ('.ipynb',),
- "html": ('.html', '.htm')
+ "html": ('.html', '.htm'),
+ # PHP files are rendered the usual way (i.e. with the full templates).
+ # The resulting files have .php extensions, making it possible to run
+ # them without reconfiguring your server to recognize them.
+ "php": ('.php',),
+ # Pandoc detects the input from the source filename
+ # but is disabled by default as it would conflict
+ # 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.
@@ -147,17 +201,34 @@ COMPILERS = {
# the posts themselves. If set to False, it will be just a list of links.
# TAG_PAGES_ARE_INDEXES = True
-# Final location is output / TRANSLATION[lang] / INDEX_PATH / index-*.html
+# 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"
-# Final locations are:
+
+# 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
@@ -169,12 +240,13 @@ COMPILERS = {
# relative URL.
#
# If you don't need any of these, just set to []
-# REDIRECTIONS = []
+REDIRECTIONS = []
# Commands to execute to deploy. Can be anything, for example,
# you may use rsync:
-# "rsync -rav output/* joe@my.site:/srv/www/site"
-# And then do a backup, or ping pingomatic.
+# "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 = []
@@ -205,14 +277,39 @@ COMPILERS = {
# argument.
#
# By default, there are no filters.
+#
+# Many filters are shipped with Nikola. A list is available in the manual:
+# <http://getnikola.com/handbook.html#post-processing-filters>
# FILTERS = {
# ".jpg": ["jpegoptim --strip-all -m75 -v %s"],
# }
-# Create a gzipped copy of each generated file. Cheap server-side optimization.
+# 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')
+# 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
@@ -224,31 +321,50 @@ COMPILERS = {
# 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_TITLE = "" # If this is empty, the default is BLOG_TITLE
-# INDEXES_PAGES = "" # If this is empty, the default is 'old posts page %d' translated
+# 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"
-# Name of the theme to use. Themes are located in themes/theme_name
-# THEME = 'site'
+# 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_CONGIF_SUBTHEME = 'sky' # You can also use: beige/serif/simple/night/default
+# 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_CONGIF_TRANSITION = 'cube' # You can also use: page/concave/linear/none/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 used to display post dates.
+# (str used by datetime.datetime.strftime)
# DATE_FORMAT = '%Y-%m-%d %H:%M'
# FAVICONS contains (name, file, size) tuples.
# Used for create favicon link like this:
# <link rel="name" href="file" sizes="size"/>
-# about favicons, see: http://www.netmagazine.com/features/create-perfect-favicon
+# 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"),
@@ -257,7 +373,17 @@ COMPILERS = {
# Show only teasers in the index pages? Defaults to False.
# INDEX_TEASERS = False
-# A HTML fragment describing the license, for the sidebar. Default is "".
+# A HTML fragment with the Read more... link.
+# The following tags exist and are replaced for you:
+# {link} A link to the full post page.
+# {read_more} The string “Read more” in the current language.
+# {{ A literal { (U+007B LEFT CURLY BRACKET)
+# }} A literal } (U+007D RIGHT CURLY BRACKET)
+# READ_MORE_LINK = '<p class="more"><a href="{link}">{read_more}…</a></p>'
+
+# A HTML fragment describing the license, for the sidebar.
+# (translatable)
+LICENSE = ""
# I recommend using the Creative Commons' wizard:
# http://creativecommons.org/choose/
# LICENSE = """
@@ -267,17 +393,48 @@ COMPILERS = {
# src="http://i.creativecommons.org/l/by-nc-sa/2.5/ar/88x31.png"></a>"""
# A small copyright notice for the page footer (in HTML).
-# Default is ''
-CONTENT_FOOTER = 'Contents &copy; {date} <a href="mailto:{email}">{author}</a> - Powered by <a href="http://nikola.ralsina.com.ar">Nikola</a>'
-CONTENT_FOOTER = CONTENT_FOOTER.format(email=BLOG_EMAIL,
- author=BLOG_AUTHOR,
- date=time.gmtime().tm_year)
-
-# To enable comments via Disqus, you need to create a forum at
-# http://disqus.com, and set DISQUS_FORUM to the short name you selected.
-# If you want to disable comments, set it to False.
-# Default is "nikolademo", used by the demo sites
-# DISQUS_FORUM = "nikolademo"
+# (translatable)
+CONTENT_FOOTER = 'Contents &copy; {date} <a href="mailto:{email}">{author}</a> - Powered by <a href="http://getnikola.com" rel="nofollow">Nikola</a> {license}'
+
+# Things that will be passed to CONTENT_FOOTER.format(). This is done
+# for translatability, as dicts are not formattable. Nikola will
+# intelligently format the setting properly.
+# The setting takes a dict. The keys are languages. The values are
+# tuples of tuples of positional arguments and dicts of keyword arguments
+# to format(). For example, {'en': (('Hello'), {'target': 'World'})}
+# results in CONTENT_FOOTER['en'].format('Hello', target='World').
+# WARNING: If you do not use multiple languages with CONTENT_FOOTER, this
+# still needs to be a dict of this format. (it can be empty if you
+# do not need formatting)
+# (translatable)
+CONTENT_FOOTER_FORMATS = {
+ DEFAULT_LANG: (
+ (),
+ {
+ "email": BLOG_EMAIL,
+ "author": BLOG_AUTHOR,
+ "date": time.gmtime().tm_year,
+ "license": LICENSE
+ }
+ )
+}
+
+# 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
@@ -286,28 +443,106 @@ CONTENT_FOOTER = CONTENT_FOOTER.format(email=BLOG_EMAIL,
# Enable comments on picture gallery pages?
# COMMENTS_IN_GALLERIES = False
+# What file should be used for directory indexes?
+# Defaults to index.html
+# Common other alternatives: default.html for IIS, index.php
+# INDEX_FILE = "index.html"
+
+# If a link ends in /index.html, drop the index.html part.
+# http://mysite/foo/bar/index.html => http://mysite/foo/bar/
+# (Uses the INDEX_FILE setting, so if that is, say, default.html,
+# it will instead /foo/default.html => /foo)
+# (Note: This was briefly STRIP_INDEX_HTML in v 5.4.3 and 5.4.4)
+# Default = False
+# STRIP_INDEXES = False
+
+# Should the sitemap list directories which only include other directories
+# and no files.
+# Default to True
+# If this is False
+# e.g. /2012 includes only /01, /02, /03, /04, ...: don't add it to the sitemap
+# if /2012 includes any files (including index.html)... add it to the sitemap
+# SITEMAP_INCLUDE_FILELESS_DIRS = True
+
+# Instead of putting files in <slug>.html, put them in
+# <slug>/index.html. Also enables STRIP_INDEXES
+# This can be disabled on a per-page/post basis by adding
+# .. pretty_url: False
+# to the metadata
+# PRETTY_URLS = False
+
+# If True, publish future dated posts right away instead of scheduling them.
+# Defaults to False.
+# FUTURE_IS_NOW = False
+
+# If True, future dated posts are allowed in deployed output
+# Only the individual posts are published/deployed; not in indexes/sitemap
+# Generally, you want FUTURE_IS_NOW and DEPLOY_FUTURE to be the same value.
+# DEPLOY_FUTURE = False
+# If False, draft posts will not be deployed
+# DEPLOY_DRAFTS = True
+
+# Allows scheduling of posts using the rule specified here (new_post -s)
+# Specify an iCal Recurrence Rule: http://www.kanzaki.com/docs/ical/rrule.html
+# SCHEDULE_RULE = ''
+# If True, use the scheduling rule to all posts by default
+# SCHEDULE_ALL = False
+# If True, schedules post to today if possible, even if scheduled hour is over
+# SCHEDULE_FORCE_TODAY = False
+
# Do you want a add a Mathjax config file?
# MATHJAX_CONFIG = ""
# If you are using the compile-ipynb plugin, just add this one:
-#MATHJAX_CONFIG = """
-#<script type="text/x-mathjax-config">
-#MathJax.Hub.Config({
-# tex2jax: {
-# inlineMath: [ ['$','$'], ["\\\(","\\\)"] ],
-# displayMath: [ ['$$','$$'], ["\\\[","\\\]"] ]
-# },
-# displayAlign: 'left', // Change this to 'center' to center equations.
-# "HTML-CSS": {
-# styles: {'.MathJax_Display': {"margin": 0}}
-# }
-#});
-#</script>
-#"""
-
-# Enable Addthis social buttons?
-# Defaults to true
-# ADD_THIS_BUTTONS = True
+# MATHJAX_CONFIG = """
+# <script type="text/x-mathjax-config">
+# MathJax.Hub.Config({
+# tex2jax: {
+# inlineMath: [ ['$','$'], ["\\\(","\\\)"] ],
+# displayMath: [ ['$$','$$'], ["\\\[","\\\]"] ]
+# },
+# displayAlign: 'left', // Change this to 'center' to center equations.
+# "HTML-CSS": {
+# styles: {'.MathJax_Display': {"margin": 0}}
+# }
+# });
+# </script>
+# """
+
+# Do you want to customize the nbconversion of your IPython notebook?
+# IPYNB_CONFIG = {}
+# With the following example configuracion you can use a custom jinja template
+# called `toggle.tpl` which has to be located in your site/blog main folder:
+# IPYNB_CONFIG = {'Exporter':{'template_file': 'toggle'}}
+
+# What MarkDown extensions to enable?
+# You will also get gist, nikola and podcast because those are
+# done in the code, hope you don't mind ;-)
+# MARKDOWN_EXTENSIONS = ['fenced_code', 'codehilite']
+
+# Social buttons. This is sample code for AddThis (which was the default for a
+# long time). Insert anything you want here, or even make it empty.
+# (translatable)
+# SOCIAL_BUTTONS_CODE = """
+# <!-- Social buttons -->
+# <div id="addthisbox" class="addthis_toolbox addthis_peekaboo_style addthis_default_style addthis_label_style addthis_32x32_style">
+# <a class="addthis_button_more">Share</a>
+# <ul><li><a class="addthis_button_facebook"></a>
+# <li><a class="addthis_button_google_plusone_share"></a>
+# <li><a class="addthis_button_linkedin"></a>
+# <li><a class="addthis_button_twitter"></a>
+# </ul>
+# </div>
+# <script src="//s7.addthis.com/js/300/addthis_widget.js#pubid=ra-4f7088a56bb93798"></script>
+# <!-- End of social buttons -->
+# """
+
+# Show link to source for the posts?
+# Formerly known as HIDE_SOURCELINK (inverse)
+# SHOW_SOURCELINK = True
+# Copy the source files for your pages?
+# Setting it to False implies SHOW_SOURCELINK = False
+# COPY_SOURCES = True
# Modify the number of Post per Index Page
# Defaults to 10
@@ -321,30 +556,73 @@ CONTENT_FOOTER = CONTENT_FOOTER.format(email=BLOG_EMAIL,
# Show only teasers in the RSS feed? Default to True
# RSS_TEASERS = True
+# Strip HTML in the RSS feed? Default to False
+# RSS_PLAIN = False
+
# A search form to search this site, for the sidebar. You can use a google
# custom search (http://www.google.com/cse/)
# Or a duckduckgo search: https://duckduckgo.com/search_box.html
# Default is no search form.
+# (translatable)
# SEARCH_FORM = ""
#
-# This search form works for any site and looks good in the "site" theme where it
-# appears on the navigation bar
-#SEARCH_FORM = """
-#<!-- Custom search -->
-#<form method="get" id="search" action="http://duckduckgo.com/"
-# class="navbar-form pull-left">
-#<input type="hidden" name="sites" value="%s"/>
-#<input type="hidden" name="k8" value="#444444"/>
-#<input type="hidden" name="k9" value="#D51920"/>
-#<input type="hidden" name="kt" value="h"/>
-#<input type="text" name="q" maxlength="255"
-# placeholder="Search&hellip;" class="span2" style="margin-top: 4px;"/>
-#<input type="submit" value="DuckDuckGo Search" style="visibility: hidden;" />
-#</form>
-#<!-- End of custom search -->
-#""" % BLOG_URL
+# This search form works for any site and looks good in the "site" theme where
+# it appears on the navigation bar:
+#
+# SEARCH_FORM = """
+# <!-- Custom search -->
+# <form method="get" id="search" action="//duckduckgo.com/"
+# class="navbar-form pull-left">
+# <input type="hidden" name="sites" value="%s"/>
+# <input type="hidden" name="k8" value="#444444"/>
+# <input type="hidden" name="k9" value="#D51920"/>
+# <input type="hidden" name="kt" value="h"/>
+# <input type="text" name="q" maxlength="255"
+# placeholder="Search&hellip;" class="span2" style="margin-top: 4px;"/>
+# <input type="submit" value="DuckDuckGo Search" style="visibility: hidden;" />
+# </form>
+# <!-- End of custom search -->
+# """ % SITE_URL
+#
+# If you prefer a google search form, here's an example that should just work:
+# SEARCH_FORM = """
+# <!-- Custom search with google-->
+# <form id="search" action="//www.google.com/search" method="get" class="navbar-form pull-left">
+# <input type="hidden" name="q" value="site:%s" />
+# <input type="text" name="q" maxlength="255" results="0" placeholder="Search"/>
+# </form>
+# <!-- End of custom search -->
+# """ % SITE_URL
+
+# Also, there is a local search plugin you can use, based on Tipue, but it requires setting several
+# options:
+
+# SEARCH_FORM = """
+# <span class="navbar-form pull-left">
+# <input type="text" id="tipue_search_input">
+# </span>"""
#
-# Also, there is a local search plugin you can use.
+# BODY_END = """
+# <script src="/assets/js/tipuesearch_set.js"></script>
+# <script src="/assets/js/tipuesearch.js"></script>
+# <script>
+# $(document).ready(function() {
+# $('#tipue_search_input').tipuesearch({
+# 'mode': 'json',
+# 'contentLocation': '/assets/js/tipuesearch_content.json',
+# 'showUrl': false
+# });
+# });
+# </script>
+# """
+
+# EXTRA_HEAD_DATA = """
+# <link rel="stylesheet" type="text/css" href="/assets/css/tipuesearch.css">
+# <div id="tipue_search_content" style="margin-left: auto; margin-right: auto; padding: 20px;"></div>
+# """
+# ENABLED_EXTRAS = ['local_search']
+#
+
# Use content distribution networks for jquery and twitter-bootstrap css and js
# If this is True, jquery is served from the Google CDN and twitter-bootstrap
@@ -353,13 +631,14 @@ CONTENT_FOOTER = CONTENT_FOOTER.format(email=BLOG_EMAIL,
# external resources.
# USE_CDN = False
-# Google analytics script or whatever else you use. Added to the bottom of <body>
+# Extra things you want in the pages HEAD tag. This will be added right
+# before </head>
+# (translatable)
+# EXTRA_HEAD_DATA = ""
+# Google Analytics or whatever else you use. Added to the bottom of <body>
# in the default template (base.tmpl).
-# ANALYTICS = ""
-
-# HTML snippet that will be added at the bottom of body of <body>
-# in the default template (base.tmpl).
-# SOCIAL_BUTTONS_CODE = ""
+# (translatable)
+# BODY_END = ""
# The possibility to extract metadata from the filename by using a
# regular expression.
@@ -376,13 +655,17 @@ CONTENT_FOOTER = CONTENT_FOOTER.format(email=BLOG_EMAIL,
# '(?P<date>\d{4}-\d{2}-\d{2})-(?P<slug>.*)-(?P<title>.*)\.md'
# FILE_METADATA_REGEXP = None
+# Additional metadata that is added to a post when creating a new_post
+# ADDITIONAL_METADATA = {}
+
# Nikola supports Twitter Card summaries / Open Graph.
# Twitter cards make it possible for you to attach media to Tweets
# that link to your content.
#
# IMPORTANT:
# Please note, that you need to opt-in for using Twitter Cards!
-# To do this please visit https://dev.twitter.com/form/participate-twitter-cards
+# 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
@@ -391,16 +674,22 @@ CONTENT_FOOTER = CONTENT_FOOTER.format(email=BLOG_EMAIL,
# 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.
+# # '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.
# }
-# If you want to use formatted post time in W3C-DTF Format(ex. 2012-03-30T23:00:00+02:00),
-# set timzone if you want a localized posted date.
+# 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 = 'Europe/Zurich'
+# TIMEZONE = 'UTC'
# If webassets is installed, bundle JS and CSS to make site loading faster
# USE_BUNDLES = True
@@ -408,7 +697,56 @@ CONTENT_FOOTER = CONTENT_FOOTER.format(email=BLOG_EMAIL,
# Plugins you don't want to use. Be careful :-)
# DISABLED_PLUGINS = ["render_galleries"]
+# Add the absolute paths to directories containing plugins to use them.
+# For example, the `plugins` directory of your clone of the Nikola plugins
+# repository.
+# EXTRA_PLUGINS_DIRS = []
+
+# Experimental plugins - use at your own risk.
+# They probably need some manual adjustments - please see their respective
+# readme.
+# ENABLED_EXTRAS = [
+# 'planetoid',
+# 'ipynb',
+# 'local_search',
+# 'render_mustache',
+# ]
+
+# List of regular expressions, links matching them will always be considered
+# valid by "nikola check -l"
+# LINK_CHECK_WHITELIST = []
+
+# If set to True, enable optional hyphenation in your posts (requires pyphen)
+# HYPHENATE = False
+
+# The <hN> tags in HTML generated by certain compilers (reST/Markdown)
+# will be demoted by that much (1 → h1 will become h2 and so on)
+# This was a hidden feature of the Markdown and reST compilers in the
+# past. Useful especially if your post titles are in <h1> tags too, for
+# example.
+# (defaults to 1.)
+# DEMOTE_HEADERS = 1
+
+# You can configure the logging handlers installed as plugins or change the
+# log level of the default stdout handler.
+LOGGING_HANDLERS = {
+ 'stderr': {'loglevel': 'WARNING', 'bubble': True},
+ # 'smtp': {
+ # 'from_addr': 'test-errors@example.com',
+ # 'recipients': ('test@example.com'),
+ # 'credentials':('testusername', 'password'),
+ # 'server_addr': ('127.0.0.1', 25),
+ # 'secure': (),
+ # 'level': 'DEBUG',
+ # 'bubble': True
+ # }
+}
+
+# Templates will use those filters, along with the defaults.
+# Consult your engine's documentation on filters if you need help defining
+# those.
+# TEMPLATE_FILTERS = {}
+
# Put in global_context things you want available on all your templates.
# It can be anything, data, functions, modules, etc.
-
GLOBAL_CONTEXT = {}
diff --git a/tests/data/translated_titles/stories/1.txt.pl b/tests/data/translated_titles/stories/1.pl.txt
index a888c1f..a888c1f 100644
--- a/tests/data/translated_titles/stories/1.txt.pl
+++ b/tests/data/translated_titles/stories/1.pl.txt
diff --git a/tests/test_command_import_wordpress.py b/tests/test_command_import_wordpress.py
index 04e0631..1a2e7d9 100644
--- a/tests/test_command_import_wordpress.py
+++ b/tests/test_command_import_wordpress.py
@@ -469,5 +469,6 @@ You can use the <a title="Jenkins Plugin: Violations" href="https://wiki.jenkins
self.assertEqual(1, len(redirections))
self.assertTrue(('somewhere/else/index.html', '/posts/somewhereelse.html') in redirections)
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/test_command_init.py b/tests/test_command_init.py
index 4332213..d2171f8 100644
--- a/tests/test_command_init.py
+++ b/tests/test_command_init.py
@@ -11,13 +11,18 @@ 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(
@@ -25,8 +30,8 @@ class CommandInitCallTest(unittest.TestCase):
create_empty_site_patch = mock.patch(
'nikola.plugins.command.init.CommandInit.create_empty_site', self.create_empty_site)
- self.patches = [copy_sample_site_patch, create_configuration_patch,
- create_empty_site_patch]
+ self.patches = [ask_questions_patch, copy_sample_site_patch,
+ create_configuration_patch, create_empty_site_patch]
for patch in self.patches:
patch.start()
@@ -42,16 +47,26 @@ class CommandInitCallTest(unittest.TestCase):
del self.create_empty_site
def test_init_default(self):
- for arguments in (dict(options={'demo': True}, args=['destination']), {}):
- self.init_command.execute(**arguments)
+ self.init_command.execute()
- self.assertTrue(self.create_configuration.called)
- self.assertTrue(self.copy_sample_site.called)
- self.assertFalse(self.create_empty_site.called)
+ 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_called_without_target(self):
- self.init_command.execute()
+ 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)
@@ -59,10 +74,34 @@ class CommandInitCallTest(unittest.TestCase):
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("""{
+ DEFAULT_LANG: "",
+ "en": "./en",
+ "es": "./es",
+}""", translations_cfg)
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/test_commands.py b/tests/test_commands.py
new file mode 100644
index 0000000..5c2370a
--- /dev/null
+++ b/tests/test_commands.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals, absolute_import
+
+# 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__), '..'))
+
+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 d51d3aa..ace17cf 100644
--- a/tests/test_compile_markdown.py
+++ b/tests/test_compile_markdown.py
@@ -15,16 +15,10 @@ import unittest
from os import path
from nikola.plugins.compile.markdown import CompileMarkdown
+from .base import BaseTestCase, FakeSite
-class FakeSite(object):
- config = {
- "MARKDOWN_EXTENSIONS": ['fenced_code', 'codehilite'],
- "LOGGING_HANDLERS": {'stderr': {'loglevel': 'WARNING', 'bubble': True}}
- }
-
-
-class CompileMarkdownTests(unittest.TestCase):
+class CompileMarkdownTests(BaseTestCase):
def setUp(self):
self.tmp_dir = tempfile.mkdtemp()
self.input_path = path.join(self.tmp_dir, 'input.markdown')
@@ -61,9 +55,9 @@ class CompileMarkdownTests(unittest.TestCase):
expected_output = '''\
<table class="codehilitetable"><tr><td class="linenos">\
<div class="linenodiv"><pre>1</pre></div>\
-</td><td class="code"><div class="code">\
-<pre><span class="kn">from</span> <span class="nn">this</span>
-</pre></div>
+</td><td class="code"><pre class="code literal-block">\
+<span class="kn">from</span> <span class="nn">this</span>
+</pre>
</td></tr></table>
'''
diff --git a/tests/test_integration.py b/tests/test_integration.py
index 44b28e9..9f982d8 100644
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -7,15 +7,15 @@ import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
-
import codecs
import locale
import shutil
+import subprocess
import tempfile
import unittest
import lxml.html
-from nose.plugins.skip import SkipTest
+import pytest
from nikola import __main__
import nikola
@@ -33,6 +33,7 @@ class EmptyBuildTest(BaseTestCase):
@classmethod
def setUpClass(cls):
"""Setup a demo site."""
+ 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()
@@ -69,6 +70,8 @@ class EmptyBuildTest(BaseTestCase):
@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
@@ -182,9 +185,18 @@ class TranslatedBuildTest(EmptyBuildTest):
def __init__(self, *a, **kw):
super(TranslatedBuildTest, self).__init__(*a, **kw)
try:
+ self.oldlocale = locale.getlocale()
locale.setlocale(locale.LC_ALL, ("pl_PL", "utf8"))
except:
- raise SkipTest
+ pytest.skip()
+
+ @classmethod
+ def tearDownClass(self):
+ try:
+ locale.setlocale(locale.LC_ALL, self.oldlocale)
+ except:
+ pass
+ super(TranslatedBuildTest, self).tearDownClass()
def test_translated_titles(self):
"""Check that translated title is picked up."""
@@ -207,15 +219,15 @@ class TranslationsPatternTest1(TranslatedBuildTest):
@classmethod
def patch_site(self):
- """Set the TRANSLATIONS_PATTERN to the new v7 default"""
- os.rename(os.path.join(self.target_dir, "stories", "1.txt.pl"),
- os.path.join(self.target_dir, "stories", "1.pl.txt")
+ """Set the TRANSLATIONS_PATTERN to the old v6 default"""
+ os.rename(os.path.join(self.target_dir, "stories", "1.pl.txt"),
+ os.path.join(self.target_dir, "stories", "1.txt.pl")
)
conf_path = os.path.join(self.target_dir, "conf.py")
with codecs.open(conf_path, "rb", "utf-8") as inf:
data = inf.read()
- data = data.replace('TRANSLATIONS_PATTERN = "{path}.{ext}.{lang}"',
- 'TRANSLATIONS_PATTERN = "{path}.{lang}.{ext}"')
+ data = data.replace('TRANSLATIONS_PATTERN = "{path}.{lang}.{ext}"',
+ 'TRANSLATIONS_PATTERN = "{path}.{ext}.{lang}"')
with codecs.open(conf_path, "wb+", "utf8") as outf:
outf.write(data)
@@ -238,15 +250,15 @@ class TranslationsPatternTest2(TranslatedBuildTest):
@classmethod
def patch_site(self):
- """Set the TRANSLATIONS_PATTERN to the new v7 default"""
+ """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.txt.pl"),
- os.path.join(self.target_dir, "stories", "1_pl.txt")
+ os.rename(os.path.join(self.target_dir, "stories", "1.pl.txt"),
+ os.path.join(self.target_dir, "stories", "1.txt.pl")
)
with codecs.open(conf_path, "rb", "utf-8") as inf:
data = inf.read()
- data = data.replace('TRANSLATIONS_PATTERN = "{path}.{ext}.{lang}"',
- 'TRANSLATIONS_PATTERN = "{path}_{lang}.{ext}"')
+ data = data.replace('TRANSLATIONS_PATTERN = "{path}.{lang}.{ext}"',
+ 'TRANSLATIONS_PATTERN = "{path}.{ext}.{lang}"')
with codecs.open(conf_path, "wb+", "utf8") as outf:
outf.write(data)
@@ -446,5 +458,49 @@ class SubdirRunningTest(DemoBuildTest):
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)
+ 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.')
+
+
if __name__ == "__main__":
unittest.main()
diff --git a/tests/test_rss_feeds.py b/tests/test_rss_feeds.py
index c43b92b..3554a38 100644
--- a/tests/test_rss_feeds.py
+++ b/tests/test_rss_feeds.py
@@ -15,18 +15,21 @@ import os
import re
import unittest
+import dateutil.tz
+from lxml import etree
import mock
-from lxml import etree
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):
@@ -71,7 +74,8 @@ class RSSFeedTest(unittest.TestCase):
[example_post,
],
'testfeed.rss',
- True)
+ True,
+ False)
opener_mock.assert_called_once_with(
'testfeed.rss', 'wb+', 'utf-8')
diff --git a/tests/test_rst_compiler.py b/tests/test_rst_compiler.py
index ae4ac06..379aba2 100644
--- a/tests/test_rst_compiler.py
+++ b/tests/test_rst_compiler.py
@@ -43,85 +43,16 @@ import tempfile
import docutils
from lxml import html
-from nose.plugins.skip import SkipTest
+import pytest
import unittest
-from yapsy.PluginManager import PluginManager
-from nikola import utils
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, STDERR_HANDLER
-from nikola.plugin_categories import (
- Command,
- Task,
- LateTask,
- TemplateSystem,
- PageCompiler,
- TaskMultiplier,
- RestExtension,
-)
-from .base import BaseTestCase
-
-
-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.config = {
- 'DISABLED_PLUGINS': [],
- 'EXTRA_PLUGINS': [],
- 'DEFAULT_LANG': '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,
- "RestExtension": RestExtension,
- })
- self.loghandlers = [STDERR_HANDLER]
- self.plugin_manager.setPluginInfoExtension('plugin')
- if sys.version_info[0] == 3:
- places = [
- os.path.join(os.path.dirname(utils.__file__), 'plugins'),
- ]
- else:
- places = [
- os.path.join(os.path.dirname(utils.__file__), utils.sys_encode('plugins')),
- ]
- self.plugin_manager.setPluginPlaces(places)
- self.plugin_manager.collectPlugins()
-
- self.timeline = [
- FakePost(title='Fake post',
- slug='fake-post')
- ]
- self.debug = True
-
- def render_template(self, name, _, context):
- return('<img src="IMG.jpg">')
+from nikola.utils import _reload
+from .base import BaseTestCase, FakeSite
class ReSTExtensionTestCase(BaseTestCase):
@@ -218,17 +149,17 @@ class GistTestCase(ReSTExtensionTestCase):
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 """
- raise SkipTest
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 """
- raise SkipTest
self.setHtmlFromRst(self.sample_without_filename)
output = 'https://gist.github.com/fake_id2.js'
self.assertHTMLContains("script", attributes={"src": output})
@@ -295,7 +226,7 @@ class VimeoTestCase(ReSTExtensionTestCase):
""" Test Vimeo iframe tag generation """
self.basic_test()
self.assertHTMLContains("iframe",
- attributes={"src": ("http://player.vimeo.com/"
+ attributes={"src": ("//player.vimeo.com/"
"video/VID"),
"height": "400", "width": "600"})
@@ -309,7 +240,7 @@ class YoutubeTestCase(ReSTExtensionTestCase):
""" Test Youtube iframe tag generation """
self.basic_test()
self.assertHTMLContains("iframe",
- attributes={"src": ("http://www.youtube.com/"
+ attributes={"src": ("//www.youtube.com/"
"embed/YID?rel=0&hd=1&"
"wmode=transparent"),
"height": "400", "width": "600"})
@@ -323,11 +254,11 @@ class ListingTestCase(ReSTExtensionTestCase):
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_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 """
diff --git a/tests/test_scheduling.py b/tests/test_scheduling.py
index c9cda42..ae1cf92 100644
--- a/tests/test_scheduling.py
+++ b/tests/test_scheduling.py
@@ -1,16 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, absolute_import
-# 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 datetime
+import locale
import os
import sys
+# 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/
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
+import dateutil.parser
+import dateutil.tz
+import pytest
from .base import BaseTestCase
-import datetime
-from nose.plugins.skip import SkipTest
+
try:
from freezegun import freeze_time
_freeze_time = True
@@ -18,127 +22,116 @@ except ImportError:
_freeze_time = False
freeze_time = lambda x: lambda y: y
-FMT = '%Y/%m/%d %H:%M:%S'
-NOW = '2013/08/22 10:00:00' # Thursday
-TODAY = datetime.datetime.strptime(NOW, FMT)
-RULE_TH = 'RRULE:FREQ=WEEKLY;BYDAY=TH'
-RULE_FR = 'RRULE:FREQ=WEEKLY;BYDAY=FR'
+_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(self):
- if not _freeze_time:
- raise SkipTest('freezegun not installed')
-
+ def setUp(cls):
d = [name for name in sys.modules if name.startswith("six.moves.")]
- self.deleted = {}
+ cls.deleted = {}
for name in d:
- self.deleted[name] = sys.modules[name]
+ cls.deleted[name] = sys.modules[name]
del sys.modules[name]
@classmethod
- def tearDown(self):
- for name, mod in self.deleted.items():
+ def tearDown(cls):
+ for name, mod in cls.deleted.items():
sys.modules[name] = mod
- @freeze_time(NOW)
+ @freeze_time(_NOW)
def test_get_date(self):
from nikola.plugins.command.new_post import get_date
- #### NOW does not match rule #########################################
- ## No last date
+ FMT = '%Y-%m-%d %H:%M:%S %Z'.format(
+ locale.nl_langinfo(locale.D_FMT),
+ locale.nl_langinfo(locale.T_FMT),
+ )
+ 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))
- self.assertEqual(expected, get_date(True, RULE_FR, force_today=True))
+ 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
+ # 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))
- self.assertEqual(expected, get_date(True, RULE_FR, date, True))
+ self.assertEqual(expected, get_date(True, RULE_FR, date, tz=UTC))
- ## Last date in the future; doesn't match rule
+ # 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))
- self.assertEqual(expected, get_date(True, RULE_FR, date, True))
+ self.assertEqual(expected, get_date(True, RULE_FR, date, tz=UTC))
- ## Last date in the past; matches rule
+ # 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))
- self.assertEqual(expected, get_date(True, RULE_FR, date, True))
+ self.assertEqual(expected, get_date(True, RULE_FR, date, tz=UTC))
- ## Last date in the future; matches rule
+ # 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))
- self.assertEqual(expected, get_date(True, RULE_FR, date, True))
-
- #### NOW matches rule ################################################
- ## Not scheduling should return NOW
- self.assertEqual(NOW, get_date(False, RULE_TH))
- ## No last date
- self.assertEqual(NOW, get_date(True, RULE_TH))
- self.assertEqual(NOW, get_date(True, RULE_TH, force_today=True))
-
- ## Last date in the past; doesn't match rule
- ### Corresponding time has already passed, today
+ 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))
- expected = TODAY.replace(day=22, hour=7).strftime(FMT)
- self.assertEqual(expected, get_date(True, RULE_TH, date, True))
- ### Corresponding time has not passed today
+ 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))
- self.assertEqual(expected, get_date(True, RULE_TH, date, True))
+ 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
+ # 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))
- self.assertEqual(expected, get_date(True, RULE_TH, date, True))
- ### Corresponding time has not passed today
+ 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))
- self.assertEqual(expected, get_date(True, RULE_TH, date, True))
+ self.assertEqual(expected, get_date(True, RULE_TH, date, tz=UTC))
- ## Last date in the past; matches rule
- ### Corresponding time has already passed, 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).strftime(FMT)
- self.assertEqual(expected, get_date(True, RULE_TH, date))
- expected = TODAY.replace(day=22, hour=7).strftime(FMT)
- self.assertEqual(expected, get_date(True, RULE_TH, date, True))
- ### Corresponding time has already passed, today; rule specifies HOUR
+ 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))
- expected = TODAY.replace(day=22, hour=9).strftime(FMT)
- self.assertEqual(expected,
- get_date(True, RULE_TH + ';BYHOUR=9', date, True))
- ### Corresponding time has not passed today
+ 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))
- self.assertEqual(expected, get_date(True, RULE_TH, date, True))
+ self.assertEqual(expected, get_date(True, RULE_TH, date, tz=UTC))
- ## Last date in the future; matches rule
- ### Corresponding time has already passed, 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).strftime(FMT)
- self.assertEqual(expected, get_date(True, RULE_TH, date))
- ### Corresponding time has not passed today
+ 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))
- self.assertEqual(expected, get_date(True, RULE_TH, date, True))
+ self.assertEqual(expected, get_date(True, RULE_TH, date, tz=UTC))
if __name__ == '__main__':
import unittest
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 5aeba19..44d309b 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -12,7 +12,7 @@ import unittest
import mock
import lxml.html
from nikola.post import get_meta
-from nikola.utils import demote_headers
+from nikola.utils import demote_headers, TranslatableSetting
class dummy(object):
@@ -234,5 +234,93 @@ class HeaderDemotionTest(unittest.TestCase):
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)
+
+
if __name__ == '__main__':
unittest.main()