New URL helper - URL consistency fixes

fixes #1765
fixes #1811
issue #1833

New UrlFor functions

- moved body of url helper to config.path.urlFor, which can generate a URL for various scenarios
- urlFor can take a string (name) or object (relativeUrl: '/') as the first
  argument - this is the first step towards issue #1833
- also added config.path.urlForPost which is async and handles getting
  permalink setting
- frontend controller, ghost_head helper, cache invalidation all now use
  urlFor or urlForPost all urls should be correct and consistent

URL Consistency Improvements

- refactored invalidateCache into cacheInvalidationHeader which returns a
  promise so that url can be generated properly by urlForPost
- moved isPost from models to schema, and refactored schema to have a tables object
- deleted posts now return the whole object, not just id and slug,
  ensuring cache invalidation header can be set on delete
- frontend controller rss and archive page redirects work properly with subdirectory
- removes {{url}} helper from admin and client, and replaced with adminUrl
  helper which also uses urlFor
- in res.locals ghostRoot becomes relativeUrl, and path is removed
This commit is contained in:
Hannah Wolfe 2014-01-03 00:37:21 +00:00
parent 903afc5660
commit af6137248d
25 changed files with 1269 additions and 397 deletions

View File

@ -29,8 +29,8 @@
return date;
});
Handlebars.registerHelper('url', function () {
return Ghost.paths.subdir;
Handlebars.registerHelper('adminUrl', function () {
return Ghost.paths.subdir + '/ghost';
});
Handlebars.registerHelper('asset', function (context, options) {

View File

@ -7,6 +7,6 @@
</div>
<button class="button-save" type="submit">Log in</button>
<section class="meta">
<a class="forgotten-password" href="{{url}}/ghost/forgotten/">Forgotten password?</a>
<a class="forgotten-password" href="{{adminUrl}}/forgotten/">Forgotten password?</a>
</section>
</form>

View File

@ -53,7 +53,7 @@
<div class="no-posts-box">
<div class="no-posts">
<h3>You Haven't Written Any Posts Yet!</h3>
<form action="{{url}}/ghost/editor/"><button class="button-add large" title="New Post">Write a new Post</button></form>
<form action="{{adminUrl}}/editor/"><button class="button-add large" title="New Post">Write a new Post</button></form>
</div>
</div>
{{/unless}}

View File

@ -6,7 +6,7 @@ var dataExport = require('../data/export'),
when = require('when'),
nodefn = require('when/node/function'),
_ = require('underscore'),
schema = require('../data/schema'),
schema = require('../data/schema').tables,
configPaths = require('../config/paths'),
api = {},

View File

@ -16,7 +16,7 @@ var _ = require('underscore'),
// ## Request Handlers
function invalidateCache(req, res, result) {
function cacheInvalidationHeader(req, result) {
var parsedUrl = req._parsedUrl.pathname.replace(/\/$/, '').split('/'),
method = req.method,
endpoint = parsedUrl[4],
@ -30,15 +30,14 @@ function invalidateCache(req, res, result) {
} else if (endpoint === 'posts') {
cacheInvalidate = "/, /page/*, /rss/, /rss/*";
if (id && jsonResult.slug) {
cacheInvalidate += ', /' + jsonResult.slug + '/';
return config.paths.urlForPost(settings, jsonResult).then(function (postUrl) {
return cacheInvalidate + ', ' + postUrl;
});
}
}
if (cacheInvalidate) {
res.set({
"X-Cache-Invalidate": cacheInvalidate
});
}
}
return when(cacheInvalidate);
}
// ### requestHandler
@ -52,8 +51,14 @@ requestHandler = function (apiMethod) {
};
return apiMethod.call(apiContext, options).then(function (result) {
invalidateCache(req, res, result);
res.json(result || {});
return cacheInvalidationHeader(req, result).then(function (header) {
if (header) {
res.set({
"X-Cache-Invalidate": header
});
}
});
}, function (error) {
var errorCode = error.errorCode || 500,
errorMsg = {error: _.isString(error) ? error : (_.isObject(error) ? error.message : 'Unknown API Error')};

View File

@ -106,9 +106,7 @@ posts = {
return canThis(this.user).remove.post(args.id).then(function () {
return when(posts.read({id : args.id, status: 'all'})).then(function (result) {
return dataProvider.Post.destroy(args.id).then(function () {
var deletedObj = {};
deletedObj.id = result.id;
deletedObj.slug = result.slug;
var deletedObj = result;
return deletedObj;
});
});

View File

@ -1,9 +1,11 @@
// Contains all path information to be used throughout
// the codebase.
var path = require('path'),
var moment = require('moment'),
path = require('path'),
when = require('when'),
url = require('url'),
_ = require('underscore'),
requireTree = require('../require-tree'),
appRoot = path.resolve(__dirname, '../../../'),
corePath = path.resolve(appRoot, 'core/'),
@ -13,8 +15,9 @@ var path = require('path'),
themeDirectories = requireTree(themePath),
pluginDirectories = requireTree(pluginPath),
localPath = '',
availableThemes,
configUrl = '',
availableThemes,
availablePlugins;
@ -45,6 +48,7 @@ function paths() {
// TODO: remove configURL and give direct access to config object?
// TODO: not called when executing tests
function update(configURL) {
configUrl = configURL;
localPath = url.parse(configURL).path;
// Remove trailing slash
@ -59,5 +63,129 @@ function update(configURL) {
});
}
// ## createUrl
// Simple url creation from a given path
// Ensures that our urls contain the subdirectory if there is one
// And are correctly formatted as either relative or absolute
// Usage:
// createUrl('/', true) -> http://my-ghost-blog.com/
// E.g. /blog/ subdir
// createUrl('/welcome-to-ghost/') -> /blog/welcome-to-ghost/
// Parameters:
// - urlPath - string which must start and end with a slash
// - absolute (optional, default:false) - boolean whether or not the url should be absolute
// Returns:
// - a URL which always ends with a slash
function createUrl(urlPath, absolute) {
urlPath = urlPath || '/';
absolute = absolute || false;
var output = '';
// create base of url, always ends without a slash
if (absolute) {
output += configUrl.replace(/\/$/, '');
} else {
output += paths().subdir;
}
// append the path, always starts and ends with a slash
output += urlPath;
return output;
}
// ## urlPathForPost
// Always sync
// Creates the url path for a post, given a post and a permalink
// Parameters:
// - post - a json object representing a post
// - permalinks - a json object containing the permalinks setting
function urlPathForPost(post, permalinks) {
var output = '',
tags = {
year: function () { return moment(post.published_at).format('YYYY'); },
month: function () { return moment(post.published_at).format('MM'); },
day: function () { return moment(post.published_at).format('DD'); },
slug: function () { return post.slug; },
id: function () { return post.id; }
};
if (post.page === 1) {
output += '/:slug/';
} else {
output += permalinks.value;
}
// replace tags like :slug or :year with actual values
output = output.replace(/(:[a-z]+)/g, function (match) {
if (_.has(tags, match.substr(1))) {
return tags[match.substr(1)]();
}
});
return output;
}
// ## urlFor
// Synchronous url creation for a given context
// Can generate a url for a named path, given path, or known object (post)
// Determines what sort of context it has been given, and delegates to the correct generation method,
// Finally passing to createUrl, to ensure any subdirectory is honoured, and the url is absolute if needed
// Usage:
// urlFor('home', true) -> http://my-ghost-blog.com/
// E.g. /blog/ subdir
// urlFor({relativeUrl: '/my-static-page/') -> /blog/my-static-page/
// E.g. if post object represents welcome post, and slugs are set to standard
// urlFor('post', {...}) -> /welcome-to-ghost/
// E.g. if post object represents welcome post, and slugs are set to date
// urlFor('post', {...}) -> /2014/01/01/welcome-to-ghost/
// Parameters:
// - context - a string, or json object describing the context for which you need a url
// - data (optional) - a json object containing data needed to generate a url
// - absolute (optional, default:false) - boolean whether or not the url should be absolute
// This is probably not the right place for this, but it's the best place for now
function urlFor(context, data, absolute) {
var urlPath = '/',
knownObjects = ['post', 'tag', 'user'],
knownPaths = {'home': '/', 'rss': '/rss/'}; // this will become really big
// Make data properly optional
if (_.isBoolean(data)) {
absolute = data;
data = null;
}
if (_.isObject(context) && context.relativeUrl) {
urlPath = context.relativeUrl;
} else if (_.isString(context) && _.indexOf(knownObjects, context) !== -1) {
// trying to create a url for an object
if (context === 'post' && data.post && data.permalinks) {
urlPath = urlPathForPost(data.post, data.permalinks);
}
// other objects are recognised but not yet supported
} else if (_.isString(context) && _.indexOf(_.keys(knownPaths), context) !== -1) {
// trying to create a url for a named path
urlPath = knownPaths[context] || '/';
}
return createUrl(urlPath, absolute);
}
// ## urlForPost
// This method is async as we have to fetch the permalinks
// Get the permalink setting and then get a URL for the given post
// Parameters
// - settings - passed reference to api.settings
// - post - a json object representing a post
// - absolute (optional, default:false) - boolean whether or not the url should be absolute
function urlForPost(settings, post, absolute) {
return settings.read('permalinks').then(function (permalinks) {
return urlFor('post', {post: post, permalinks: permalinks}, absolute);
});
}
module.exports = paths;
module.exports.update = update;
module.exports.urlFor = urlFor;
module.exports.urlForPost = urlForPost;

View File

@ -25,19 +25,14 @@ frontendControllers = {
postsPerPage,
options = {};
api.settings.read('postsPerPage').then(function (postPP) {
postsPerPage = parseInt(postPP.value, 10);
// No negative pages
if (isNaN(pageParam) || pageParam < 1) {
//redirect to 404 page?
return res.redirect('/');
}
options.page = pageParam;
// No negative pages, or page 1
if (isNaN(pageParam) || pageParam < 1 || (pageParam === 1 && req.route.path === '/page/:page/')) {
return res.redirect(config.paths().subdir + '/');
}
// Redirect '/page/1/' to '/' for all teh good SEO
if (pageParam === 1 && req.route.path === '/page/:page/') {
return res.redirect(config.paths().subdir + '/');
}
return api.settings.read('postsPerPage').then(function (postPP) {
postsPerPage = parseInt(postPP.value, 10);
options.page = pageParam;
// No negative posts per page, must be number
if (!isNaN(postsPerPage) && postsPerPage > 0) {
@ -138,39 +133,39 @@ frontendControllers = {
},
'rss': function (req, res, next) {
// Initialize RSS
var siteUrl = config().url,
pageParam = req.params.page !== undefined ? parseInt(req.params.page, 10) : 1,
var pageParam = req.params.page !== undefined ? parseInt(req.params.page, 10) : 1,
feed;
// No negative pages, or page 1
if (isNaN(pageParam) || pageParam < 1 || (pageParam === 1 && req.route.path === '/rss/:page/')) {
return res.redirect(config.paths().subdir + '/rss/');
}
//needs refact for multi user to not use first user as default
when.settle([
return when.settle([
api.users.read({id : 1}),
api.settings.read('title'),
api.settings.read('description')
api.settings.read('description'),
api.settings.read('permalinks')
]).then(function (result) {
var user = result[0].value,
title = result[1].value.value,
description = result[2].value.value;
description = result[2].value.value,
permalinks = result[3].value,
siteUrl = config.paths.urlFor('home', null, true),
feedUrl = config.paths.urlFor('rss', null, true);
feed = new RSS({
title: title,
description: description,
generator: 'Ghost v' + res.locals.version,
author: user ? user.name : null,
feed_url: url.resolve(siteUrl, '/rss/'),
feed_url: feedUrl,
site_url: siteUrl,
ttl: '60'
});
// No negative pages
if (isNaN(pageParam) || pageParam < 1) {
return res.redirect(config.paths().subdir + '/rss/');
}
if (pageParam === 1 && req.route.path === config.paths().subdir + '/rss/:page/') {
return res.redirect(config.paths().subdir + '/rss/');
}
api.posts.browse({page: pageParam}).then(function (page) {
return api.posts.browse({page: pageParam}).then(function (page) {
var maxPage = page.pages,
feedItems = [];
@ -187,37 +182,35 @@ frontendControllers = {
filters.doFilter('prePostsRender', page.posts).then(function (posts) {
posts.forEach(function (post) {
var deferred = when.defer();
post.url = coreHelpers.url;
post.url().then(function (postUrl) {
var item = {
title: _.escape(post.title),
guid: post.uuid,
url: siteUrl + postUrl,
date: post.published_at,
categories: _.pluck(post.tags, 'name')
},
content = post.html;
var deferred = when.defer(),
item = {
title: _.escape(post.title),
guid: post.uuid,
url: config.paths.urlFor('post', {post: post, permalinks: permalinks}, true),
date: post.published_at,
categories: _.pluck(post.tags, 'name')
},
content = post.html;
//set img src to absolute url
content = content.replace(/src=["|'|\s]?([\w\/\?\$\.\+\-;%:@&=,_]+)["|'|\s]?/gi, function (match, p1) {
/*jslint unparam:true*/
p1 = url.resolve(siteUrl, p1);
return "src='" + p1 + "' ";
});
//set a href to absolute url
content = content.replace(/href=["|'|\s]?([\w\/\?\$\.\+\-;%:@&=,_]+)["|'|\s]?/gi, function (match, p1) {
/*jslint unparam:true*/
p1 = url.resolve(siteUrl, p1);
return "href='" + p1 + "' ";
});
item.description = content;
feed.item(item);
deferred.resolve();
//set img src to absolute url
content = content.replace(/src=["|'|\s]?([\w\/\?\$\.\+\-;%:@&=,_]+)["|'|\s]?/gi, function (match, p1) {
/*jslint unparam:true*/
p1 = url.resolve(siteUrl, p1);
return "src='" + p1 + "' ";
});
//set a href to absolute url
content = content.replace(/href=["|'|\s]?([\w\/\?\$\.\+\-;%:@&=,_]+)["|'|\s]?/gi, function (match, p1) {
/*jslint unparam:true*/
p1 = url.resolve(siteUrl, p1);
return "href='" + p1 + "' ";
});
item.description = content;
feed.item(item);
deferred.resolve();
feedItems.push(deferred.promise);
});
});
when.all(feedItems).then(function () {
res.set('Content-Type', 'text/xml');
res.send(feed.xml());

View File

@ -2,7 +2,7 @@ var when = require('when'),
_ = require('underscore'),
migration = require('../migration'),
knex = require('../../models/base').knex,
schema = require('../schema'),
schema = require('../schema').tables,
excludedTables = ['sessions'],
exporter;

View File

@ -8,7 +8,7 @@ var _ = require('underscore'),
defaultSettings = require('../default-settings'),
Settings = require('../../models/settings').Settings,
fixtures = require('../fixtures'),
schema = require('../schema'),
schema = require('../schema').tables,
initialVersion = '000',
schemaTables = _.keys(schema),

View File

@ -118,4 +118,12 @@ var db = {
}
};
module.exports = db;
function isPost(jsonData) {
return jsonData.hasOwnProperty('html') && jsonData.hasOwnProperty('markdown')
&& jsonData.hasOwnProperty('title') && jsonData.hasOwnProperty('slug');
}
module.exports.tables = db;
module.exports.checks = {
isPost: isPost
};

View File

@ -10,8 +10,8 @@ var downsize = require('downsize'),
config = require('../config'),
errors = require('../errorHandling'),
filters = require('../filters'),
models = require('../models'),
template = require('./template'),
schema = require('../data/schema').checks,
assetTemplate = _.template('<%= source %>?v=<%= version %>'),
scriptTemplate = _.template('<script src="<%= source %>?v=<%= version %>"></script>'),
@ -80,7 +80,7 @@ coreHelpers.encode = function (context, str) {
//
coreHelpers.pageUrl = function (context, block) {
/*jslint unparam:true*/
return context === 1 ? '/' : ('/page/' + context + '/');
return config.paths().subdir + (context === 1 ? '/' : ('/page/' + context + '/'));
};
// ### URL helper
@ -93,36 +93,13 @@ coreHelpers.pageUrl = function (context, block) {
// i.e. If inside a post context will return post permalink
// absolute flag outputs absolute URL, else URL is relative
coreHelpers.url = function (options) {
var output = '',
self = this,
tags = {
year: function () { return moment(self.published_at).format('YYYY'); },
month: function () { return moment(self.published_at).format('MM'); },
day: function () { return moment(self.published_at).format('DD'); },
slug: function () { return self.slug; },
id: function () { return self.id; }
},
isAbsolute = options && options.hash.absolute;
return api.settings.read('permalinks').then(function (permalinks) {
if (isAbsolute) {
output += config().url.replace(/\/$/, '');
} else {
output += config.paths().subdir;
}
if (models.isPost(self)) {
if (self.page === 1) {
output += '/:slug/';
} else {
output += permalinks.value;
}
output = output.replace(/(:[a-z]+)/g, function (match) {
if (_.has(tags, match.substr(1))) {
return tags[match.substr(1)]();
}
});
}
return output;
});
var absolute = options && options.hash.absolute;
if (schema.isPost(this)) {
return config.paths.urlForPost(api.settings, this, absolute);
}
return when(config.paths.urlFor(this, absolute));
};
// ### Asset helper
@ -317,9 +294,9 @@ coreHelpers.body_class = function (options) {
tags = this.post && this.post.tags ? this.post.tags : this.tags || [],
page = this.post && this.post.page ? this.post.page : this.page || false;
if (_.isString(this.ghostRoot) && this.ghostRoot.match(/\/page/)) {
if (_.isString(this.relativeUrl) && this.relativeUrl.match(/\/page/)) {
classes.push('archive-template');
} else if (!this.ghostRoot || this.ghostRoot === '/' || this.ghostRoot === '') {
} else if (!this.relativeUrl || this.relativeUrl === '/' || this.relativeUrl === '') {
classes.push('home-template');
} else {
classes.push('post-template');
@ -366,7 +343,8 @@ coreHelpers.post_class = function (options) {
coreHelpers.ghost_head = function (options) {
/*jslint unparam:true*/
var blog = config.theme(),
var self = this,
blog = config.theme(),
head = [],
majorMinor = /^(\d+\.)?(\d+)/,
trimmedVersion = this.version;
@ -376,13 +354,13 @@ coreHelpers.ghost_head = function (options) {
head.push('<meta name="generator" content="Ghost ' + trimmedVersion + '" />');
head.push('<link rel="alternate" type="application/rss+xml" title="'
+ _.escape(blog.title) + '" href="' + config.paths().subdir + '/rss/' + '">');
+ _.escape(blog.title) + '" href="' + config.paths.urlFor('rss') + '">');
if (this.ghostRoot) {
head.push('<link rel="canonical" href="' + config().url + this.ghostRoot + '" />');
}
return coreHelpers.url.call(self, {hash: {absolute: true}}).then(function (url) {
head.push('<link rel="canonical" href="' + url + '" />');
return filters.doFilter('ghost_head', head).then(function (head) {
return filters.doFilter('ghost_head', head);
}).then(function (head) {
var headString = _.reduce(head, function (memo, item) { return memo + '\n' + item; }, '');
return new hbs.handlebars.SafeString(headString.trim());
});
@ -408,8 +386,8 @@ coreHelpers.meta_title = function (options) {
var title,
blog;
if (_.isString(this.ghostRoot)) {
if (!this.ghostRoot || this.ghostRoot === '/' || this.ghostRoot === '' || this.ghostRoot.match(/\/page/)) {
if (_.isString(this.relativeUrl)) {
if (!this.relativeUrl || this.relativeUrl === '/' || this.relativeUrl === '' || this.relativeUrl.match(/\/page/)) {
blog = config.theme();
title = blog.title;
} else {
@ -428,8 +406,8 @@ coreHelpers.meta_description = function (options) {
var description,
blog;
if (_.isString(this.ghostRoot)) {
if (!this.ghostRoot || this.ghostRoot === '/' || this.ghostRoot === '' || this.ghostRoot.match(/\/page/)) {
if (_.isString(this.relativeUrl)) {
if (!this.relativeUrl || this.relativeUrl === '/' || this.relativeUrl === '' || this.relativeUrl.match(/\/page/)) {
blog = config.theme();
description = blog.description;
} else {
@ -572,6 +550,16 @@ coreHelpers.helperMissing = function (arg) {
errors.logError('Missing helper: "' + arg + '"');
};
// ## Admin URL helper
// uses urlFor to generate a URL for either the admin or the frontend.
coreHelpers.adminUrl = function (options) {
var absolute = options && options.hash && options.hash.absolute,
// Ghost isn't a named route as currently it violates the must start-and-end with slash rule
context = !options || !options.hash || !options.hash.frontend ? {relativeUrl: '/ghost'} : 'home';
return config.paths.urlFor(context, absolute);
};
// Register an async handlebars helper for a given handlebars instance
function registerAsyncHelper(hbs, name, fn) {
hbs.registerAsyncHelper(name, function (options, cb) {
@ -607,7 +595,6 @@ function registerAsyncAdminHelper(name, fn) {
}
registerHelpers = function (adminHbs, assetHash) {
// Expose hbs instance for admin
@ -662,7 +649,7 @@ registerHelpers = function (adminHbs, assetHash) {
registerAdminHelper('fileStorage', coreHelpers.fileStorage);
registerAsyncAdminHelper('url', coreHelpers.url);
registerAdminHelper('adminUrl', coreHelpers.adminUrl);
};

View File

@ -33,9 +33,8 @@ function ghostLocals(req, res, next) {
// Make sure we have a locals value.
res.locals = res.locals || {};
res.locals.version = packageInfo.version;
res.locals.path = req.path;
// Strip off the subdir part of the path
res.locals.ghostRoot = req.path.replace(config.paths().subdir, '');
// relative path from the URL, not including subdir
res.locals.relativeUrl = req.path.replace(config.paths().subdir, '');
if (res.isAdmin) {
res.locals.csrfToken = req.csrfToken();

View File

@ -35,9 +35,5 @@ module.exports = {
});
});
});
},
isPost: function (jsonData) {
return jsonData.hasOwnProperty('html') && jsonData.hasOwnProperty('markdown')
&& jsonData.hasOwnProperty('title') && jsonData.hasOwnProperty('slug');
}
};

View File

@ -5,7 +5,7 @@
<section class="content-filter">
<small>All Posts</small>
</section>
<a href="{{url}}/ghost/editor/" class="button button-add" title="New Post"><span class="hidden">New Post</span></a>
<a href="{{adminUrl}}/editor/" class="button button-add" title="New Post"><span class="hidden">New Post</span></a>
</header>
<section class="content-list-content">
<ol></ol>

View File

@ -20,12 +20,12 @@
<fieldset>
<div class="form-group">
<label>Export</label>
<a href="{{url}}/ghost/api/v0.1/db/" class="button-save">Export</a>
<a href="{{adminUrl}}/api/v0.1/db/" class="button-save">Export</a>
<p>Export the blog settings and data.</p>
</div>
</fieldset>
</form>
<form id="settings-import" method="post" action="{{url}}/ghost/api/v0.1/db/" enctype="multipart/form-data">
<form id="settings-import" method="post" action="{{adminUrl}}/api/v0.1/db/" enctype="multipart/form-data">
<input type="hidden" name="_csrf" value="{{csrfToken}}" />
<fieldset>
<div class="form-group">

View File

@ -1,9 +1,11 @@
<header id="global-header" class="navbar">
<a class="ghost-logo" href="{{url absolute="true"}}" data-off-canvas="left" title="{{url absolute="true"}}"><span class="hidden">Ghost </span></a>{{! TODO: Change this to actual Ghost homepage }}
<a class="ghost-logo" href="{{adminUrl absolute="true" frontend="true"}}" data-off-canvas="left" title="{{adminUrl absolute="true" frontend="true"}}">
<span class="hidden">Ghost </span>
</a>
<nav id="global-nav" role="navigation">
<ul id="main-menu" >
{{#each adminNav}}
<li class="{{navClass}}{{#if selected}} active{{/if}}"><a href="{{url}}/ghost{{path}}">{{name}}</a></li>
<li class="{{navClass}}{{#if selected}} active{{/if}}"><a href="{{adminUrl}}{{path}}">{{name}}</a></li>
{{/each}}
<li id="usermenu" class="subnav">
@ -12,11 +14,11 @@
<span class="name">{{#if currentUser.name}}{{currentUser.name}}{{else}}{{currentUser.email}}{{/if}}</span>
</a>
<ul class="overlay">
<li class="usermenu-profile"><a href="{{url}}/ghost/settings/user/">Your Profile</a></li>
<li class="usermenu-profile"><a href="{{adminUrl}}/settings/user/">Your Profile</a></li>
<li class="divider"></li>
<li class="usermenu-help"><a href="http://ghost.org/forum/">Help / Support</a></li>
<li class="divider"></li>
<li class="usermenu-signout"><a href="{{url}}/ghost/signout/">Sign Out</a></li>
<li class="usermenu-signout"><a href="{{adminUrl}}/signout/">Sign Out</a></li>
</ul>
</li>
</ul>

View File

@ -1,10 +1,10 @@
/*globals describe, before, beforeEach, afterEach, it */
/*globals describe, before, after, beforeEach, afterEach, it */
var testUtils = require('../../utils'),
should = require('should'),
_ = require('underscore'),
request = require('request');
request = request.defaults({jar:true});
request = request.defaults({jar: true});
describe('Post API', function () {
@ -41,284 +41,375 @@ describe('Post API', function () {
});
// ## Browse
describe('Browse', function () {
it('retrieves all published posts only by default', function (done) {
request.get(testUtils.API.getApiURL('posts/'), function (error, response, body) {
response.should.have.status(200);
should.not.exist(response.headers['x-cache-invalidate']);
response.should.be.json;
var jsonResponse = JSON.parse(body);
jsonResponse.posts.should.exist;
testUtils.API.checkResponse(jsonResponse, 'posts');
jsonResponse.posts.should.have.length(5);
testUtils.API.checkResponse(jsonResponse.posts[0], 'post');
done();
it('retrieves all published posts only by default', function (done) {
request.get(testUtils.API.getApiURL('posts/'), function (error, response, body) {
response.should.have.status(200);
should.not.exist(response.headers['x-cache-invalidate']);
response.should.be.json;
var jsonResponse = JSON.parse(body);
jsonResponse.posts.should.exist;
testUtils.API.checkResponse(jsonResponse, 'posts');
jsonResponse.posts.should.have.length(5);
testUtils.API.checkResponse(jsonResponse.posts[0], 'post');
done();
});
});
});
it('can retrieve all published posts and pages', function (done) {
request.get(testUtils.API.getApiURL('posts/?staticPages=all'), function (error, response, body) {
response.should.have.status(200);
should.not.exist(response.headers['x-cache-invalidate']);
response.should.be.json;
var jsonResponse = JSON.parse(body);
jsonResponse.posts.should.exist;
testUtils.API.checkResponse(jsonResponse, 'posts');
jsonResponse.posts.should.have.length(6);
testUtils.API.checkResponse(jsonResponse.posts[0], 'post');
done();
it('can retrieve all published posts and pages', function (done) {
request.get(testUtils.API.getApiURL('posts/?staticPages=all'), function (error, response, body) {
response.should.have.status(200);
should.not.exist(response.headers['x-cache-invalidate']);
response.should.be.json;
var jsonResponse = JSON.parse(body);
jsonResponse.posts.should.exist;
testUtils.API.checkResponse(jsonResponse, 'posts');
jsonResponse.posts.should.have.length(6);
testUtils.API.checkResponse(jsonResponse.posts[0], 'post');
done();
});
});
});
// Test bits of the API we don't use in the app yet to ensure the API behaves properly
// Test bits of the API we don't use in the app yet to ensure the API behaves properly
it('can retrieve all status posts and pages', function (done) {
request.get(testUtils.API.getApiURL('posts/?staticPages=all&status=all'), function (error, response, body) {
response.should.have.status(200);
should.not.exist(response.headers['x-cache-invalidate']);
response.should.be.json;
var jsonResponse = JSON.parse(body);
jsonResponse.posts.should.exist;
testUtils.API.checkResponse(jsonResponse, 'posts');
jsonResponse.posts.should.have.length(8);
testUtils.API.checkResponse(jsonResponse.posts[0], 'post');
done();
it('can retrieve all status posts and pages', function (done) {
request.get(testUtils.API.getApiURL('posts/?staticPages=all&status=all'), function (error, response, body) {
response.should.have.status(200);
should.not.exist(response.headers['x-cache-invalidate']);
response.should.be.json;
var jsonResponse = JSON.parse(body);
jsonResponse.posts.should.exist;
testUtils.API.checkResponse(jsonResponse, 'posts');
jsonResponse.posts.should.have.length(8);
testUtils.API.checkResponse(jsonResponse.posts[0], 'post');
done();
});
});
});
it('can retrieve just published pages', function (done) {
request.get(testUtils.API.getApiURL('posts/?staticPages=true'), function (error, response, body) {
response.should.have.status(200);
should.not.exist(response.headers['x-cache-invalidate']);
response.should.be.json;
var jsonResponse = JSON.parse(body);
jsonResponse.posts.should.exist;
testUtils.API.checkResponse(jsonResponse, 'posts');
jsonResponse.posts.should.have.length(1);
testUtils.API.checkResponse(jsonResponse.posts[0], 'post');
done();
it('can retrieve just published pages', function (done) {
request.get(testUtils.API.getApiURL('posts/?staticPages=true'), function (error, response, body) {
response.should.have.status(200);
should.not.exist(response.headers['x-cache-invalidate']);
response.should.be.json;
var jsonResponse = JSON.parse(body);
jsonResponse.posts.should.exist;
testUtils.API.checkResponse(jsonResponse, 'posts');
jsonResponse.posts.should.have.length(1);
testUtils.API.checkResponse(jsonResponse.posts[0], 'post');
done();
});
});
});
it('can retrieve just draft posts', function (done) {
request.get(testUtils.API.getApiURL('posts/?status=draft'), function (error, response, body) {
response.should.have.status(200);
should.not.exist(response.headers['x-cache-invalidate']);
response.should.be.json;
var jsonResponse = JSON.parse(body);
jsonResponse.posts.should.exist;
testUtils.API.checkResponse(jsonResponse, 'posts');
jsonResponse.posts.should.have.length(1);
testUtils.API.checkResponse(jsonResponse.posts[0], 'post');
done();
it('can retrieve just draft posts', function (done) {
request.get(testUtils.API.getApiURL('posts/?status=draft'), function (error, response, body) {
response.should.have.status(200);
should.not.exist(response.headers['x-cache-invalidate']);
response.should.be.json;
var jsonResponse = JSON.parse(body);
jsonResponse.posts.should.exist;
testUtils.API.checkResponse(jsonResponse, 'posts');
jsonResponse.posts.should.have.length(1);
testUtils.API.checkResponse(jsonResponse.posts[0], 'post');
done();
});
});
});
// ## Read
it('can retrieve a post', function (done) {
request.get(testUtils.API.getApiURL('posts/1/'), function (error, response, body) {
response.should.have.status(200);
should.not.exist(response.headers['x-cache-invalidate']);
response.should.be.json;
var jsonResponse = JSON.parse(body);
jsonResponse.should.exist;
testUtils.API.checkResponse(jsonResponse, 'post');
jsonResponse.page.should.eql(0);
done();
});
});
it('can retrieve a static page', function (done) {
request.get(testUtils.API.getApiURL('posts/7/'), function (error, response, body) {
response.should.have.status(200);
should.not.exist(response.headers['x-cache-invalidate']);
response.should.be.json;
var jsonResponse = JSON.parse(body);
jsonResponse.should.exist;
testUtils.API.checkResponse(jsonResponse, 'post');
jsonResponse.page.should.eql(1);
done();
});
});
it('can\'t retrieve non existent post', function (done) {
request.get(testUtils.API.getApiURL('posts/99/'), function (error, response, body) {
response.should.have.status(404);
should.not.exist(response.headers['x-cache-invalidate']);
response.should.be.json;
var jsonResponse = JSON.parse(body);
jsonResponse.should.exist;
testUtils.API.checkResponseValue(jsonResponse, ['error']);
done();
});
});
it('can\'t retrieve a draft post', function (done) {
request.get(testUtils.API.getApiURL('posts/5/'), function (error, response, body) {
response.should.have.status(404);
should.not.exist(response.headers['x-cache-invalidate']);
response.should.be.json;
var jsonResponse = JSON.parse(body);
jsonResponse.should.exist;
testUtils.API.checkResponseValue(jsonResponse, ['error']);
done();
});
});
it('can\'t retrieve a draft page', function (done) {
request.get(testUtils.API.getApiURL('posts/8/'), function (error, response, body) {
response.should.have.status(404);
should.not.exist(response.headers['x-cache-invalidate']);
response.should.be.json;
var jsonResponse = JSON.parse(body);
jsonResponse.should.exist;
testUtils.API.checkResponseValue(jsonResponse, ['error']);
done();
});
});
// ## Add
it('can create a new draft, publish post, update post', function (done) {
var newTitle = 'My Post',
changedTitle = 'My Post changed',
publishedState = 'published',
newPost = {status: 'draft', title: newTitle, markdown: 'my post'};
request.post({uri: testUtils.API.getApiURL('posts/'),
headers: {'X-CSRF-Token': csrfToken},
json: newPost}, function (error, response, draftPost) {
response.should.have.status(200);
//TODO: do drafts really need a x-cache-invalidate header
response.should.be.json;
draftPost.should.exist;
draftPost.title.should.eql(newTitle);
draftPost.status = publishedState;
testUtils.API.checkResponse(draftPost, 'post');
request.put({uri: testUtils.API.getApiURL('posts/' + draftPost.id + '/'),
headers: {'X-CSRF-Token': csrfToken},
json: draftPost}, function (error, response, publishedPost) {
describe('Read', function () {
it('can retrieve a post', function (done) {
request.get(testUtils.API.getApiURL('posts/1/'), function (error, response, body) {
response.should.have.status(200);
response.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /' + publishedPost.slug + '/');
should.not.exist(response.headers['x-cache-invalidate']);
response.should.be.json;
publishedPost.should.exist;
publishedPost.title.should.eql(newTitle);
publishedPost.status.should.eql(publishedState);
testUtils.API.checkResponse(publishedPost, 'post');
request.put({uri: testUtils.API.getApiURL('posts/' + publishedPost.id + '/'),
var jsonResponse = JSON.parse(body);
jsonResponse.should.exist;
testUtils.API.checkResponse(jsonResponse, 'post');
jsonResponse.page.should.eql(0);
done();
});
});
it('can retrieve a static page', function (done) {
request.get(testUtils.API.getApiURL('posts/7/'), function (error, response, body) {
response.should.have.status(200);
should.not.exist(response.headers['x-cache-invalidate']);
response.should.be.json;
var jsonResponse = JSON.parse(body);
jsonResponse.should.exist;
testUtils.API.checkResponse(jsonResponse, 'post');
jsonResponse.page.should.eql(1);
done();
});
});
it('can\'t retrieve non existent post', function (done) {
request.get(testUtils.API.getApiURL('posts/99/'), function (error, response, body) {
response.should.have.status(404);
should.not.exist(response.headers['x-cache-invalidate']);
response.should.be.json;
var jsonResponse = JSON.parse(body);
jsonResponse.should.exist;
testUtils.API.checkResponseValue(jsonResponse, ['error']);
done();
});
});
it('can\'t retrieve a draft post', function (done) {
request.get(testUtils.API.getApiURL('posts/5/'), function (error, response, body) {
response.should.have.status(404);
should.not.exist(response.headers['x-cache-invalidate']);
response.should.be.json;
var jsonResponse = JSON.parse(body);
jsonResponse.should.exist;
testUtils.API.checkResponseValue(jsonResponse, ['error']);
done();
});
});
it('can\'t retrieve a draft page', function (done) {
request.get(testUtils.API.getApiURL('posts/8/'), function (error, response, body) {
response.should.have.status(404);
should.not.exist(response.headers['x-cache-invalidate']);
response.should.be.json;
var jsonResponse = JSON.parse(body);
jsonResponse.should.exist;
testUtils.API.checkResponseValue(jsonResponse, ['error']);
done();
});
});
});
// ## Add
describe('Add', function () {
it('can create a new draft, publish post, update post', function (done) {
var newTitle = 'My Post',
changedTitle = 'My Post changed',
publishedState = 'published',
newPost = {status: 'draft', title: newTitle, markdown: 'my post'};
request.post({uri: testUtils.API.getApiURL('posts/'),
headers: {'X-CSRF-Token': csrfToken},
json: newPost}, function (error, response, draftPost) {
response.should.have.status(200);
//TODO: do drafts really need a x-cache-invalidate header
response.should.be.json;
draftPost.should.exist;
draftPost.title.should.eql(newTitle);
draftPost.status = publishedState;
testUtils.API.checkResponse(draftPost, 'post');
request.put({uri: testUtils.API.getApiURL('posts/' + draftPost.id + '/'),
headers: {'X-CSRF-Token': csrfToken},
json: publishedPost}, function (error, response, updatedPost) {
json: draftPost}, function (error, response, publishedPost) {
response.should.have.status(200);
response.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /' + updatedPost.slug + '/');
response.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /' + publishedPost.slug + '/');
response.should.be.json;
updatedPost.should.exist;
updatedPost.title.should.eql(newTitle);
testUtils.API.checkResponse(updatedPost, 'post');
done();
publishedPost.should.exist;
publishedPost.title.should.eql(newTitle);
publishedPost.status.should.eql(publishedState);
testUtils.API.checkResponse(publishedPost, 'post');
request.put({uri: testUtils.API.getApiURL('posts/' + publishedPost.id + '/'),
headers: {'X-CSRF-Token': csrfToken},
json: publishedPost}, function (error, response, updatedPost) {
response.should.have.status(200);
response.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /' + updatedPost.slug + '/');
response.should.be.json;
updatedPost.should.exist;
updatedPost.title.should.eql(newTitle);
testUtils.API.checkResponse(updatedPost, 'post');
done();
});
});
});
});
});
// ## edit
describe('Edit', function () {
it('can edit a post', function (done) {
request.get(testUtils.API.getApiURL('posts/1/'), function (error, response, body) {
var jsonResponse = JSON.parse(body),
changedValue = 'My new Title';
jsonResponse.should.exist;
jsonResponse.title = changedValue;
it('can edit a post', function (done) {
request.get(testUtils.API.getApiURL('posts/1/'), function (error, response, body) {
var jsonResponse = JSON.parse(body),
changedValue = 'My new Title';
jsonResponse.should.exist;
jsonResponse.title = changedValue;
request.put({uri: testUtils.API.getApiURL('posts/1/'),
headers: {'X-CSRF-Token': csrfToken},
json: jsonResponse}, function (error, response, putBody) {
response.should.have.status(200);
response.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /' + putBody.slug + '/');
response.should.be.json;
putBody.should.exist;
putBody.title.should.eql(changedValue);
request.put({uri: testUtils.API.getApiURL('posts/1/'),
headers: {'X-CSRF-Token': csrfToken},
json: jsonResponse}, function (error, response, putBody) {
response.should.have.status(200);
response.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /' + putBody.slug + '/');
response.should.be.json;
putBody.should.exist;
putBody.title.should.eql(changedValue);
testUtils.API.checkResponse(putBody, 'post');
done();
testUtils.API.checkResponse(putBody, 'post');
done();
});
});
});
});
// ## delete
describe('Delete', function () {
it('can\'t edit non existent post', function (done) {
request.get(testUtils.API.getApiURL('posts/1/'), function (error, response, body) {
var jsonResponse = JSON.parse(body),
changedValue = 'My new Title';
jsonResponse.title.exist;
jsonResponse.testvalue = changedValue;
jsonResponse.id = 99;
request.put({uri: testUtils.API.getApiURL('posts/99/'),
headers: {'X-CSRF-Token': csrfToken},
json: jsonResponse}, function (error, response, putBody) {
response.should.have.status(404);
should.not.exist(response.headers['x-cache-invalidate']);
response.should.be.json;
testUtils.API.checkResponseValue(putBody, ['error']);
done();
});
});
});
it('can\'t edit non existent post', function (done) {
request.get(testUtils.API.getApiURL('posts/1/'), function (error, response, body) {
var jsonResponse = JSON.parse(body),
changedValue = 'My new Title';
jsonResponse.title.exist;
jsonResponse.testvalue = changedValue;
jsonResponse.id = 99;
request.put({uri: testUtils.API.getApiURL('posts/99/'),
headers: {'X-CSRF-Token': csrfToken},
json: jsonResponse}, function (error, response, putBody) {
it('can delete a post', function (done) {
var deletePostId = 1;
request.del({uri: testUtils.API.getApiURL('posts/' + deletePostId + '/'),
headers: {'X-CSRF-Token': csrfToken}}, function (error, response, body) {
response.should.have.status(200);
response.should.be.json;
var jsonResponse = JSON.parse(body);
jsonResponse.should.exist;
response.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /' + jsonResponse.slug + '/');
testUtils.API.checkResponse(jsonResponse, 'post');
jsonResponse.id.should.eql(deletePostId);
done();
});
});
it('can\'t delete a non existent post', function (done) {
request.del({uri: testUtils.API.getApiURL('posts/99/'),
headers: {'X-CSRF-Token': csrfToken}}, function (error, response, body) {
response.should.have.status(404);
should.not.exist(response.headers['x-cache-invalidate']);
response.should.be.json;
testUtils.API.checkResponseValue(putBody, ['error']);
var jsonResponse = JSON.parse(body);
jsonResponse.should.exist;
testUtils.API.checkResponseValue(jsonResponse, ['error']);
done();
});
});
it('can delete a new draft', function (done) {
var newTitle = 'My Post',
publishedState = 'draft',
newPost = {status: publishedState, title: newTitle, markdown: 'my post'};
request.post({uri: testUtils.API.getApiURL('posts/'),
headers: {'X-CSRF-Token': csrfToken},
json: newPost}, function (error, response, draftPost) {
response.should.have.status(200);
//TODO: do drafts really need a x-cache-invalidate header
response.should.be.json;
draftPost.should.exist;
draftPost.title.should.eql(newTitle);
draftPost.status = publishedState;
testUtils.API.checkResponse(draftPost, 'post');
request.del({uri: testUtils.API.getApiURL('posts/' + draftPost.id + '/'),
headers: {'X-CSRF-Token': csrfToken}}, function (error, response, body) {
response.should.have.status(200);
//TODO: do drafts really need a x-cache-invalidate header
response.should.be.json;
var jsonResponse = JSON.parse(body);
jsonResponse.should.exist;
testUtils.API.checkResponse(jsonResponse, 'post');
done();
});
});
});
});
it('can delete a post', function (done) {
var deletePostId = 1;
request.del({uri: testUtils.API.getApiURL('posts/' + deletePostId + '/'),
headers: {'X-CSRF-Token': csrfToken}}, function (error, response, body) {
response.should.have.status(200);
response.should.be.json;
var jsonResponse = JSON.parse(body);
jsonResponse.should.exist;
response.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /' + jsonResponse.slug + '/');
testUtils.API.checkResponseValue(jsonResponse, ['id', 'slug']);
jsonResponse.id.should.eql(deletePostId);
done();
describe('Dated Permalinks', function () {
before(function (done) {
request.get(testUtils.API.getApiURL('settings'), function (error, response, body) {
if (error) { done(error); }
var jsonResponse = JSON.parse(body);
jsonResponse.permalinks = '/:year/:month/:day/:slug/';
request.put({
uri: testUtils.API.getApiURL('settings/'),
headers: {'X-CSRF-Token': csrfToken},
json: jsonResponse
}, function (error, response, putBody) {
if (error) { done(error); }
done();
});
});
});
});
it('can\'t delete a non existent post', function (done) {
request.del({uri: testUtils.API.getApiURL('posts/99/'),
headers: {'X-CSRF-Token': csrfToken}}, function (error, response, body) {
response.should.have.status(404);
should.not.exist(response.headers['x-cache-invalidate']);
response.should.be.json;
var jsonResponse = JSON.parse(body);
jsonResponse.should.exist;
testUtils.API.checkResponseValue(jsonResponse, ['error']);
done();
after(function (done) {
request.get(testUtils.API.getApiURL('settings'), function (error, response, body) {
if (error) { done(error); }
var jsonResponse = JSON.parse(body);
jsonResponse.permalinks = '/:slug/';
request.put({
uri: testUtils.API.getApiURL('settings/'),
headers: {'X-CSRF-Token': csrfToken},
json: jsonResponse
}, function (error, response, putBody) {
if (error) { done(error); }
done();
});
});
});
});
it('can delete a new draft', function (done) {
var newTitle = 'My Post',
publishedState = 'draft',
newPost = {status: publishedState, title: newTitle, markdown: 'my post'};
request.post({uri: testUtils.API.getApiURL('posts/'),
headers: {'X-CSRF-Token': csrfToken},
json: newPost}, function (error, response, draftPost) {
response.should.have.status(200);
//TODO: do drafts really need a x-cache-invalidate header
response.should.be.json;
draftPost.should.exist;
draftPost.title.should.eql(newTitle);
draftPost.status = publishedState;
testUtils.API.checkResponse(draftPost, 'post');
request.del({uri: testUtils.API.getApiURL('posts/' + draftPost.id + '/'),
headers: {'X-CSRF-Token': csrfToken}}, function (error, response, body) {
it('Can read a post', function (done) {
// nothing should have changed here
request.get(testUtils.API.getApiURL('posts/2/'), function (error, response, body) {
if (error) { done(error); }
response.should.have.status(200);
//TODO: do drafts really need a x-cache-invalidate header
should.not.exist(response.headers['x-cache-invalidate']);
response.should.be.json;
var jsonResponse = JSON.parse(body);
jsonResponse.should.exist;
testUtils.API.checkResponseValue(jsonResponse, ['id', 'slug']);
testUtils.API.checkResponse(jsonResponse, 'post');
jsonResponse.slug.should.not.match(/^\/[0-9]{4}\/[0-9]{2}\/[0-9]{2}/);
jsonResponse.page.should.eql(0);
done();
});
});
it('Can edit a post', function (done) {
request.get(testUtils.API.getApiURL('posts/2/'), function (error, response, body) {
if (error) { done(error); }
var jsonResponse = JSON.parse(body),
changedValue = 'My new Title';
jsonResponse.should.exist;
jsonResponse.title = changedValue;
request.put({
uri: testUtils.API.getApiURL('posts/2/'),
headers: {'X-CSRF-Token': csrfToken},
json: jsonResponse
}, function (error, response, putBody) {
if (error) { done(error); }
var today = new Date(),
dd = ("0" + today.getDate()).slice(-2),
mm = ("0" + (today.getMonth() + 1)).slice(-2),
yyyy = today.getFullYear(),
postLink = '/' + yyyy + '/' + mm + '/' + dd + '/' + putBody.slug + '/';
response.should.have.status(200);
response.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, ' + postLink);
response.should.be.json;
putBody.should.exist;
putBody.title.should.eql(changedValue);
testUtils.API.checkResponse(putBody, 'post');
done();
});
});
});
});
});

View File

@ -3,11 +3,12 @@
*/
/*globals url, CasperTest, casper */
CasperTest.begin('Ensure that RSS is available', 11, function suite(test) {
CasperTest.Routines.togglePermalinks.run('off');
casper.thenOpen(url + 'rss/', function (response) {
var content = this.getPageContent(),
siteTitle = '<title><![CDATA[Ghost]]></title>',
siteDescription = '<description><![CDATA[Just a blogging platform.]]></description>',
siteUrl = '<link>http://127.0.0.1:2369</link>',
siteUrl = '<link>http://127.0.0.1:2369/</link>',
postTitle = '<![CDATA[Welcome to Ghost]]>',
postStart = '<description><![CDATA[<p>You\'re live!',
postEnd = 'you think :)</p>]]></description>',
@ -41,4 +42,5 @@ CasperTest.begin('Ensures dated permalinks works with RSS', 2, function suite(te
test.assertEqual(response.status, 200, 'Response status should be 200.');
test.assert(content.indexOf(postLink) >= 0, 'Feed should have dated permalink.');
});
CasperTest.Routines.togglePermalinks.run('off');
}, false);

View File

@ -19,4 +19,60 @@ CasperTest.begin('Test helpers on homepage', 3, function suite(test) {
test.assertExists('article.post', 'post_class outputs correct post class');
test.assertExists('article.tag-getting-started', 'post_class outputs correct tag class');
});
}, true);
}, true);
CasperTest.begin('Test navigating to Post', 4, function suite(test) {
casper.thenOpen(url, function then(response) {
var lastPost = '.content article:last-of-type',
lastPostLink = lastPost + ' .post-title a';
test.assertExists(lastPost, 'there is a last child on the page');
test.assertSelectorHasText(lastPostLink, 'Welcome to Ghost', 'Is correct post');
casper.then(function testLink() {
var link = this.evaluate(function (lastPostLink) {
return document.querySelector(lastPostLink).getAttribute('href');
}, lastPostLink);
test.assert(link === '/welcome-to-ghost/', 'Has correct link');
});
casper.thenClick(lastPostLink);
casper.waitForResource(/welcome-to-ghost/).then(function (resource) {
test.assert(resource.status === 200, 'resource got 200');
});
});
}, true);
CasperTest.begin('Test navigating to Post with date permalink', 4, function suite(test) {
CasperTest.Routines.togglePermalinks.run('on');
casper.thenOpen(url, function then(response) {
var lastPost = '.content article:last-of-type',
lastPostLink = lastPost + ' .post-title a',
today = new Date(),
dd = ("0" + today.getDate()).slice(-2),
mm = ("0" + (today.getMonth() + 1)).slice(-2),
yyyy = today.getFullYear(),
postLink = '/' + yyyy + '/' + mm + '/' + dd + '/welcome-to-ghost/';
test.assertExists(lastPost, 'there is a last child on the page');
test.assertSelectorHasText(lastPostLink, 'Welcome to Ghost', 'Is correct post');
casper.then(function testLink() {
var link = this.evaluate(function (lastPostLink) {
return document.querySelector(lastPostLink).getAttribute('href');
}, lastPostLink);
test.assert(link === postLink, 'Has correct link');
});
casper.thenClick(lastPostLink);
casper.waitForResource(postLink).then(function (resource) {
test.assert(resource.status === 200, 'resource got 200');
});
});
CasperTest.Routines.togglePermalinks.run('off');
}, false);

View File

@ -6,20 +6,21 @@
// Tests when permalinks is set to date
CasperTest.begin('Post page does not load as slug', 2, function suite(test) {
casper.start(url + 'welcome-to-ghost', function then(response) {
CasperTest.Routines.togglePermalinks.run('on');
casper.thenOpen(url + 'welcome-to-ghost', function then(response) {
test.assertTitle('404 — Page Not Found', 'The post should return 404 page');
test.assertElementCount('.content .post', 0, 'There is no post on this page');
});
}, true);
CasperTest.Routines.togglePermalinks.run('off');
}, false);
CasperTest.begin('Post page loads', 3, function suite(test) {
CasperTest.Routines.togglePermalinks.run('off');
casper.thenOpen(url + 'welcome-to-ghost', function then(response) {
test.assertTitle('Welcome to Ghost', 'The post should have a title and it should be "Welcome to Ghost"');
test.assertElementCount('.content .post', 1, 'There is exactly one post on this page');
test.assertSelectorHasText('.poweredby', 'Proudly published with Ghost');
});
}, false);
}, true);
CasperTest.begin('Test helpers on welcome post', 4, function suite(test) {
casper.start(url + 'welcome-to-ghost', function then(response) {

View File

@ -289,7 +289,6 @@ describe('Frontend Routing', function () {
// // get today's date
// var date = moment().format("YYYY/MM/DD");
//
// console.log('date', date);
//
// request.get('/' + date + '/welcome-to-ghost/')
// .expect(200)

View File

@ -8,6 +8,8 @@ var should = require('should'),
_ = require('underscore'),
rewire = require("rewire"),
testUtils = require('../utils'),
// Thing we are testing
defaultConfig = require('../../../config.example')[process.env.NODE_ENV],
loader = rewire('../../server/config/loader'),
@ -274,11 +276,10 @@ describe('Config', function () {
});
afterEach(function (done) {
sandbox.restore();
paths.update(defaultConfig.url)
.then(done)
.then(null, done);
sandbox.restore();
});
it('should have exactly the right keys', function () {
@ -346,4 +347,243 @@ describe('Config', function () {
}).otherwise(done);
});
});
describe('urlFor', function () {
afterEach(function (done) {
paths.update(defaultConfig.url)
.then(done)
.then(null, done);
});
it('should return the home url with no options', function (done) {
paths.urlFor().should.equal('/');
paths.update('http://my-ghost-blog.com/blog').then(function () {
paths.urlFor().should.equal('/blog/');
done();
});
});
it('should return home url when asked for', function (done) {
var testContext = 'home';
paths.update('http://my-ghost-blog.com').then(function () {
paths.urlFor(testContext).should.equal('/');
paths.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/');
return paths.update('http://my-ghost-blog.com/blog');
}).then(function () {
paths.urlFor(testContext).should.equal('/blog/');
paths.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/blog/');
done();
});
});
it('should return rss url when asked for', function (done) {
var testContext = 'rss';
paths.update('http://my-ghost-blog.com').then(function () {
paths.urlFor(testContext).should.equal('/rss/');
paths.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/rss/');
return paths.update('http://my-ghost-blog.com/blog');
}).then(function () {
paths.urlFor(testContext).should.equal('/blog/rss/');
paths.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/blog/rss/');
done();
});
});
it('should return url for a random path when asked for', function (done) {
var testContext = {relativeUrl: '/about/'};
paths.update('http://my-ghost-blog.com').then(function () {
paths.urlFor(testContext).should.equal('/about/');
paths.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/about/');
return paths.update('http://my-ghost-blog.com/blog');
}).then(function () {
paths.urlFor(testContext).should.equal('/blog/about/');
paths.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/blog/about/');
done();
});
});
it('should return url for a post when asked for', function (done) {
var testContext = 'post',
testData = {post: testUtils.DataGenerator.Content.posts[2], permalinks: {value: '/:slug/'}};
paths.update('http://my-ghost-blog.com').then(function () {
paths.urlFor(testContext, testData).should.equal('/short-and-sweet/');
paths.urlFor(testContext, testData, true).should.equal('http://my-ghost-blog.com/short-and-sweet/');
return paths.update('http://my-ghost-blog.com/blog');
}).then(function () {
paths.urlFor(testContext, testData).should.equal('/blog/short-and-sweet/');
paths.urlFor(testContext, testData, true).should.equal('http://my-ghost-blog.com/blog/short-and-sweet/');
done();
}).then(null, done);
});
it('should return url for a dated post when asked for', function (done) {
var testContext = 'post',
testData = {
post: testUtils.DataGenerator.Content.posts[2],
permalinks: {value: '/:year/:month/:day/:slug/'}
},
today = new Date(),
dd = ("0" + today.getDate()).slice(-2),
mm = ("0" + (today.getMonth() + 1)).slice(-2),
yyyy = today.getFullYear(),
postLink = '/' + yyyy + '/' + mm + '/' + dd + '/short-and-sweet/';
paths.update('http://my-ghost-blog.com').then(function () {
paths.urlFor(testContext, testData).should.equal(postLink);
paths.urlFor(testContext, testData, true).should.equal('http://my-ghost-blog.com' + postLink);
return paths.update('http://my-ghost-blog.com/blog');
}).then(function () {
paths.urlFor(testContext, testData).should.equal('/blog' + postLink);
paths.urlFor(testContext, testData, true).should.equal('http://my-ghost-blog.com/blog' + postLink);
done();
}).then(null, done);
});
});
describe('urlForPost', function () {
var sandbox;
beforeEach(function () {
sandbox = sinon.sandbox.create();
});
afterEach(function (done) {
sandbox.restore();
paths.update(defaultConfig.url)
.then(done)
.then(null, done);
});
it('should output correct url for post', function (done) {
var settings = {'read': function read() {}},
settingsStub = sandbox.stub(settings, 'read', function () {
return when({value: '/:slug/'});
}),
testData = testUtils.DataGenerator.Content.posts[2],
postLink = '/short-and-sweet/';
paths.update('http://my-ghost-blog.com').then(function () {
// next test
return paths.urlForPost(settings, testData);
}).then(function (url) {
url.should.equal(postLink);
// next test
return paths.urlForPost(settings, testData, true);
}).then(function (url) {
url.should.equal('http://my-ghost-blog.com' + postLink);
return paths.update('http://my-ghost-blog.com/blog');
}).then(function () {
// next test
return paths.urlForPost(settings, testData);
}).then(function (url) {
url.should.equal('/blog' + postLink);
// next test
return paths.urlForPost(settings, testData, true);
}).then(function (url) {
url.should.equal('http://my-ghost-blog.com/blog' + postLink);
done();
}).then(null, done);
});
it('should output correct url for post with date permalink', function (done) {
var settings = {'read': function read() {}},
settingsStub = sandbox.stub(settings, 'read', function () {
return when({value: '/:year/:month/:day/:slug/'});
}),
testData = testUtils.DataGenerator.Content.posts[2],
today = new Date(),
dd = ("0" + today.getDate()).slice(-2),
mm = ("0" + (today.getMonth() + 1)).slice(-2),
yyyy = today.getFullYear(),
postLink = '/' + yyyy + '/' + mm + '/' + dd + '/short-and-sweet/';
paths.update('http://my-ghost-blog.com').then(function () {
// next test
return paths.urlForPost(settings, testData);
}).then(function (url) {
url.should.equal(postLink);
// next test
return paths.urlForPost(settings, testData, true);
}).then(function (url) {
url.should.equal('http://my-ghost-blog.com' + postLink);
return paths.update('http://my-ghost-blog.com/blog');
}).then(function () {
// next test
return paths.urlForPost(settings, testData);
}).then(function (url) {
url.should.equal('/blog' + postLink);
// next test
return paths.urlForPost(settings, testData, true);
}).then(function (url) {
url.should.equal('http://my-ghost-blog.com/blog' + postLink);
done();
}).then(null, done);
});
it('should output correct url for page with date permalink', function (done) {
var settings = {'read': function read() {}},
settingsStub = sandbox.stub(settings, 'read', function () {
return when({value: '/:year/:month/:day/:slug/'});
}),
testData = testUtils.DataGenerator.Content.posts[5],
postLink = '/static-page-test/';
paths.update('http://my-ghost-blog.com').then(function () {
// next test
return paths.urlForPost(settings, testData);
}).then(function (url) {
url.should.equal(postLink);
// next test
return paths.urlForPost(settings, testData, true);
}).then(function (url) {
url.should.equal('http://my-ghost-blog.com' + postLink);
return paths.update('http://my-ghost-blog.com/blog');
}).then(function () {
// next test
return paths.urlForPost(settings, testData);
}).then(function (url) {
url.should.equal('/blog' + postLink);
// next test
return paths.urlForPost(settings, testData, true);
}).then(function (url) {
url.should.equal('http://my-ghost-blog.com/blog' + postLink);
done();
}).then(null, done);
});
});
});

View File

@ -26,8 +26,106 @@ describe('Frontend Controller', function () {
});
describe('homepage', function () {
// No tests yet, shows up in coverage report
describe('homepage redirects', function () {
var res;
beforeEach(function () {
res = {
redirect: sandbox.spy(),
render: sandbox.spy()
};
sandbox.stub(api.posts, 'browse', function () {
return when({posts: {}, pages: 3});
});
apiSettingsStub = sandbox.stub(api.settings, 'read');
apiSettingsStub.withArgs('postsPerPage').returns(when({
'key': 'postsPerPage',
'value': 6
}));
});
it('Redirects to home if page number is 0', function () {
var req = {params: {page: -1}, route: {path: '/page/:page/'}};
frontend.homepage(req, res, null);
res.redirect.called.should.be.true;
res.redirect.calledWith('/').should.be.true;
res.render.called.should.be.false;
});
it('Redirects to home if page number is 0', function () {
var req = {params: {page: 0}, route: {path: '/page/:page/'}};
frontend.homepage(req, res, null);
res.redirect.called.should.be.true;
res.redirect.calledWith('/').should.be.true;
res.render.called.should.be.false;
});
it('Redirects to home if page number is 1', function () {
var req = {params: {page: 1}, route: {path: '/page/:page/'}};
frontend.homepage(req, res, null);
res.redirect.called.should.be.true;
res.redirect.calledWith('/').should.be.true;
res.render.called.should.be.false;
});
it('Redirects to home if page number is 0 with subdirectory', function () {
sandbox.stub(config, 'paths', function () { return {subdir: '/blog'}; });
var req = {params: {page: 0}, route: {path: '/page/:page/'}};
frontend.homepage(req, res, null);
res.redirect.called.should.be.true;
res.redirect.calledWith('/blog/').should.be.true;
res.render.called.should.be.false;
});
it('Redirects to home if page number is 1 with subdirectory', function () {
sandbox.stub(config, 'paths', function () { return {subdir: '/blog'}; });
var req = {params: {page: 1}, route: {path: '/page/:page/'}};
frontend.homepage(req, res, null);
res.redirect.called.should.be.true;
res.redirect.calledWith('/blog/').should.be.true;
res.render.called.should.be.false;
});
it('Redirects to last page if page number too big', function (done) {
var req = {params: {page: 4}, route: {path: '/page/:page/'}};
frontend.homepage(req, res, done).then(function () {
res.redirect.called.should.be.true;
res.redirect.calledWith('/page/3/').should.be.true;
res.render.called.should.be.false;
done();
});
});
it('Redirects to last page if page number too big with subdirectory', function (done) {
sandbox.stub(config, 'paths', function () { return {subdir: '/blog'}; });
var req = {params: {page: 4}, route: {path: '/page/:page/'}};
frontend.homepage(req, res, done).then(function () {
res.redirect.calledOnce.should.be.true;
res.redirect.calledWith('/blog/page/3/').should.be.true;
res.render.called.should.be.false;
done();
});
});
});
describe('single', function () {
@ -358,4 +456,118 @@ describe('Frontend Controller', function () {
});
});
});
describe('rss redirects', function () {
var res,
apiUsersStub;
beforeEach(function () {
res = {
locals: { version: '' },
redirect: sandbox.spy(),
render: sandbox.spy()
};
sandbox.stub(api.posts, 'browse', function () {
return when({posts: {}, pages: 3});
});
apiUsersStub = sandbox.stub(api.users, 'read').returns(when({}));
apiSettingsStub = sandbox.stub(api.settings, 'read');
apiSettingsStub.withArgs('title').returns(when({
'key': 'title',
'value': 'Test'
}));
apiSettingsStub.withArgs('description').returns(when({
'key': 'description',
'value': 'Some Text'
}));
apiSettingsStub.withArgs('permalinks').returns(when({
'key': 'permalinks',
'value': '/:slug/'
}));
});
it('Redirects to rss if page number is 0', function () {
var req = {params: {page: -1}, route: {path: '/rss/:page/'}};
frontend.rss(req, res, null);
res.redirect.called.should.be.true;
res.redirect.calledWith('/rss/').should.be.true;
res.render.called.should.be.false;
});
it('Redirects to rss if page number is 0', function () {
var req = {params: {page: 0}, route: {path: '/rss/:page/'}};
frontend.rss(req, res, null);
res.redirect.called.should.be.true;
res.redirect.calledWith('/rss/').should.be.true;
res.render.called.should.be.false;
});
it('Redirects to home if page number is 1', function () {
var req = {params: {page: 1}, route: {path: '/rss/:page/'}};
frontend.rss(req, res, null);
res.redirect.called.should.be.true;
res.redirect.calledWith('/rss/').should.be.true;
res.render.called.should.be.false;
});
it('Redirects to home if page number is 0 with subdirectory', function () {
sandbox.stub(config, 'paths', function () { return {subdir: '/blog'}; });
var req = {params: {page: 0}, route: {path: '/rss/:page/'}};
frontend.rss(req, res, null);
res.redirect.called.should.be.true;
res.redirect.calledWith('/blog/rss/').should.be.true;
res.render.called.should.be.false;
});
it('Redirects to home if page number is 1 with subdirectory', function () {
sandbox.stub(config, 'paths', function () { return {subdir: '/blog'}; });
var req = {params: {page: 1}, route: {path: '/rss/:page/'}};
frontend.rss(req, res, null);
res.redirect.called.should.be.true;
res.redirect.calledWith('/blog/rss/').should.be.true;
res.render.called.should.be.false;
});
it('Redirects to last page if page number too big', function (done) {
var req = {params: {page: 4}, route: {path: '/rss/:page/'}};
frontend.rss(req, res, done).then(function () {
res.redirect.called.should.be.true;
res.redirect.calledWith('/rss/3/').should.be.true;
res.render.called.should.be.false;
done();
});
});
it('Redirects to last page if page number too big with subdirectory', function (done) {
sandbox.stub(config, 'paths', function () { return {subdir: '/blog'}; });
var req = {params: {page: 4}, route: {path: '/rss/:page/'}};
frontend.rss(req, res, done).then(function () {
res.redirect.calledOnce.should.be.true;
res.redirect.calledWith('/blog/rss/3/').should.be.true;
res.render.called.should.be.false;
done();
});
});
});
});

View File

@ -12,7 +12,7 @@ var testUtils = require('../utils'),
// Stuff we are testing
handlebars = hbs.handlebars,
helpers = require('../../server/helpers'),
helpers = rewire('../../server/helpers'),
config = require('../../server/config');
describe('Core Helpers', function () {
@ -23,6 +23,7 @@ describe('Core Helpers', function () {
beforeEach(function (done) {
var adminHbs = hbs.create();
helpers = rewire('../../server/helpers');
sandbox = sinon.sandbox.create();
apiStub = sandbox.stub(api.settings, 'read', function () {
return when({value: 'casper'});
@ -244,9 +245,9 @@ describe('Core Helpers', function () {
it('can render class string for context', function (done) {
when.all([
helpers.body_class.call({ghostRoot: '/'}),
helpers.body_class.call({ghostRoot: '/a-post-title'}),
helpers.body_class.call({ghostRoot: '/page/4'})
helpers.body_class.call({relativeUrl: '/'}),
helpers.body_class.call({relativeUrl: '/a-post-title'}),
helpers.body_class.call({relativeUrl: '/page/4'})
]).then(function (rendered) {
rendered.length.should.equal(3);
@ -264,7 +265,7 @@ describe('Core Helpers', function () {
it('can render class for static page', function (done) {
helpers.body_class.call({
ghostRoot: '/',
relativeUrl: '/',
post: {
page: true
}
@ -300,28 +301,82 @@ describe('Core Helpers', function () {
done();
}).then(null, done);
});
it('can render page class', function (done) {
var post = { page: true };
helpers.post_class.call(post).then(function (rendered) {
should.exist(rendered);
rendered.string.should.equal('post page');
done();
}).then(null, done);
});
});
describe('ghost_head Helper', function () {
// TODO: these tests should be easier to do!
var configUrl = config().url;
afterEach(function (done) {
config.paths.update(configUrl).then(function () {
done();
}).then(null, done);
});
it('has loaded ghost_head helper', function () {
should.exist(handlebars.helpers.ghost_head);
});
it('returns meta tag string', function (done) {
helpers.ghost_head.call({version: "0.3.0"}).then(function (rendered) {
should.exist(rendered);
rendered.string.should.equal('<meta name="generator" content="Ghost 0.3" />\n<link rel="alternate" type="application/rss+xml" title="Ghost" href="/rss/">');
config.paths.update('http://testurl.com/').then(function () {
helpers.ghost_head.call({version: "0.3.0"}).then(function (rendered) {
should.exist(rendered);
rendered.string.should.equal('<meta name="generator" content="Ghost 0.3" />\n' +
'<link rel="alternate" type="application/rss+xml" title="Ghost" href="/rss/">\n' +
'<link rel="canonical" href="http://testurl.com/" />');
done();
done();
});
}).then(null, done);
});
it('returns meta tag string even if version is invalid', function (done) {
helpers.ghost_head.call({version: "0.9"}).then(function (rendered) {
should.exist(rendered);
rendered.string.should.equal('<meta name="generator" content="Ghost 0.9" />\n<link rel="alternate" type="application/rss+xml" title="Ghost" href="/rss/">');
config.paths.update('http://testurl.com/').then(function () {
return helpers.ghost_head.call({version: "0.9"}).then(function (rendered) {
should.exist(rendered);
rendered.string.should.equal('<meta name="generator" content="Ghost 0.9" />\n' +
'<link rel="alternate" type="application/rss+xml" title="Ghost" href="/rss/">\n' +
'<link rel="canonical" href="http://testurl.com/" />');
done();
done();
});
}).then(null, done);
});
it('returns correct rss url with subdirectory', function (done) {
config.paths.update('http://testurl.com/blog/').then(function () {
return helpers.ghost_head.call({version: "0.3.0"}).then(function (rendered) {
should.exist(rendered);
rendered.string.should.equal('<meta name="generator" content="Ghost 0.3" />\n' +
'<link rel="alternate" type="application/rss+xml" title="Ghost" href="/blog/rss/">\n' +
'<link rel="canonical" href="http://testurl.com/blog/" />');
done();
});
}).then(null, done);
});
it('returns canonical URL', function (done) {
config.paths.update('http://testurl.com').then(function () {
return helpers.ghost_head.call({version: "0.3.0", relativeUrl: '/about/'}).then(function (rendered) {
should.exist(rendered);
rendered.string.should.equal('<meta name="generator" content="Ghost 0.3" />\n' +
'<link rel="alternate" type="application/rss+xml" title="Ghost" href="/rss/">\n' +
'<link rel="canonical" href="http://testurl.com/about/" />');
done();
});
}).then(null, done);
});
});
@ -392,6 +447,13 @@ describe('Core Helpers', function () {
helpers.pageUrl(2).should.equal('/page/2/');
helpers.pageUrl(50).should.equal('/page/50/');
});
it('can return a valid url with subdirectory', function () {
sandbox.stub(config, 'paths', function () { return {'subdir': '/blog'}; });
helpers.pageUrl(1).should.equal('/blog/');
helpers.pageUrl(2).should.equal('/blog/page/2/');
helpers.pageUrl(50).should.equal('/blog/page/50/');
});
});
describe("Pagination helper", function () {
@ -565,22 +627,22 @@ describe('Core Helpers', function () {
});
it('can return blog title', function (done) {
helpers.meta_title.call({ghostRoot: '/'}).then(function (rendered) {
helpers.meta_title.call({relativeUrl: '/'}).then(function (rendered) {
should.exist(rendered);
rendered.string.should.equal('Ghost');
done();
}, done);
}).then(null, done);
});
it('can return title of a post', function (done) {
var post = {ghostRoot: '/nice-post', post: {title: 'Post Title'}};
var post = {relativeUrl: '/nice-post', post: {title: 'Post Title'}};
helpers.meta_title.call(post).then(function (rendered) {
should.exist(rendered);
rendered.string.should.equal('Post Title');
done();
}, done);
}).then(null, done);
});
});
@ -591,22 +653,22 @@ describe('Core Helpers', function () {
});
it('can return blog description', function (done) {
helpers.meta_description.call({ghostRoot: '/'}).then(function (rendered) {
helpers.meta_description.call({relativeUrl: '/'}).then(function (rendered) {
should.exist(rendered);
rendered.string.should.equal('Just a blogging platform.');
done();
}, done);
}).then(null, done);
});
it('can return empty description on post', function (done) {
var post = {ghostRoot: '/nice-post', post: {title: 'Post Title'}};
var post = {relativeUrl: '/nice-post', post: {title: 'Post Title'}};
helpers.meta_description.call(post).then(function (rendered) {
should.exist(rendered);
rendered.string.should.equal('');
done();
}, done);
}).then(null, done);
});
});
@ -722,7 +784,6 @@ describe('Core Helpers', function () {
beforeEach(function () {
// set the asset hash
helpers = rewire('../../server/helpers');
helpers.assetHash = 'abc';
});
@ -782,4 +843,98 @@ describe('Core Helpers', function () {
});
});
describe('adminUrl', function () {
var rendered,
configUrl = config().url;
afterEach(function (done) {
config.paths.update(configUrl).then(function () {
done();
}).then(null, done);
});
it('should output the path to admin', function () {
rendered = helpers.adminUrl();
should.exist(rendered);
rendered.should.equal('/ghost');
});
it('should output the path to admin with subdirectory', function (done) {
config.paths.update('http://testurl.com/blog/').then(function () {
rendered = helpers.adminUrl();
should.exist(rendered);
rendered.should.equal('/blog/ghost');
done();
});
});
it('should output absolute path if absolute is set', function (done) {
// no trailing slash
config.paths.update('http://testurl.com').then(function () {
rendered = helpers.adminUrl({"hash": {absolute: true}});
should.exist(rendered);
rendered.should.equal('http://testurl.com/ghost');
// test trailing slash
return config.paths.update('http://testurl.com/');
}).then(function () {
rendered = helpers.adminUrl({"hash": {absolute: true}});
should.exist(rendered);
rendered.should.equal('http://testurl.com/ghost');
done();
});
});
it('should output absolute path with subdirectory', function (done) {
config.paths.update('http://testurl.com/blog').then(function () {
rendered = helpers.adminUrl({"hash": {absolute: true}});
should.exist(rendered);
rendered.should.equal('http://testurl.com/blog/ghost');
done();
});
});
it('should output the path to frontend if frontend is set', function () {
rendered = helpers.adminUrl({"hash": {frontend: true}});
should.exist(rendered);
rendered.should.equal('/');
});
it('should output the absolute path to frontend if both are set', function (done) {
config.paths.update('http://testurl.com').then(function () {
rendered = helpers.adminUrl({"hash": {frontend: true, absolute: true}});
should.exist(rendered);
rendered.should.equal('http://testurl.com/');
return config.paths.update('http://testurl.com/');
}).then(function () {
rendered = helpers.adminUrl({"hash": {frontend: true, absolute: true}});
should.exist(rendered);
rendered.should.equal('http://testurl.com/');
done();
});
});
it('should output the path to frontend with subdirectory', function (done) {
config.paths.update('http://testurl.com/blog/').then(function () {
rendered = helpers.adminUrl({"hash": {frontend: true}});
should.exist(rendered);
rendered.should.equal('/blog/');
done();
});
});
it('should output the absolute path to frontend with subdirectory', function (done) {
config.paths.update('http://testurl.com/blog/').then(function () {
rendered = helpers.adminUrl({"hash": {frontend: true, absolute: true}});
should.exist(rendered);
rendered.should.equal('http://testurl.com/blog/');
done();
});
});
});
});