diff options
| author | 2014-06-13 21:51:02 -0300 | |
|---|---|---|
| committer | 2014-06-13 21:51:02 -0300 | |
| commit | 58c4878526dec5510f23c812274686787d8724ba (patch) | |
| tree | 5f2374bc17adb10e15f7e5b4576595d9cc2ef17e /tests | |
| parent | fa50632a9d87c3989566fed3e49c160a132e0d14 (diff) | |
Imported Upstream version 7.0.1upstream/7.0.1
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/README.rst | 5 | ||||
| -rw-r--r-- | tests/__init__.py | 25 | ||||
| -rw-r--r-- | tests/base.py | 79 | ||||
| -rw-r--r-- | tests/conftest.py | 8 | ||||
| -rw-r--r-- | tests/data/translated_titles/conf.py | 578 | ||||
| -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.py | 1 | ||||
| -rw-r--r-- | tests/test_command_init.py | 57 | ||||
| -rw-r--r-- | tests/test_commands.py | 18 | ||||
| -rw-r--r-- | tests/test_compile_markdown.py | 16 | ||||
| -rw-r--r-- | tests/test_integration.py | 82 | ||||
| -rw-r--r-- | tests/test_rss_feeds.py | 10 | ||||
| -rw-r--r-- | tests/test_rst_compiler.py | 93 | ||||
| -rw-r--r-- | tests/test_scheduling.py | 145 | ||||
| -rw-r--r-- | tests/test_utils.py | 90 |
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 © {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 © {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…" 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…" 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() |
