Clean up paginated URL generation

refs #5091, #6612

- unify getNextUrl & getPrevUrl into getPaginatedUrl
- ensure that it can generate a prev, next or exact page no url
- ensure that it can figure out the base url
- use the same code from the page_url helper
- refactor the tests to ensure there's 100% coverage

Following on from #6612, this ensures that pagination always works regardless of whether the channel is default or custom
This commit is contained in:
Hannah Wolfe 2016-03-20 21:48:15 +00:00
parent ef0945b09f
commit 5f2c913fc1
10 changed files with 257 additions and 305 deletions

View File

@ -1,8 +1,7 @@
var config = require('../../config'),
getUrl = require('./url'),
getCanonicalUrl = require('./canonical_url'),
getPreviousUrl = require('./previous_url'),
getNextUrl = require('./next_url'),
getPaginatedUrl = require('./paginated_url'),
getAuthorUrl = require('./author_url'),
getRssUrl = require('./rss_url'),
getTitle = require('./title'),
@ -23,8 +22,8 @@ function getMetaData(data, root) {
metaData = {
url: getUrl(data, true),
canonicalUrl: getCanonicalUrl(data),
previousUrl: getPreviousUrl(data, true),
nextUrl: getNextUrl(data, true),
previousUrl: getPaginatedUrl('prev', data, true),
nextUrl: getPaginatedUrl('next', data, true),
authorUrl: getAuthorUrl(data, true),
rssUrl: getRssUrl(data, true),
metaTitle: getTitle(data, root),

View File

@ -1,23 +0,0 @@
var config = require('../../config'),
trimmedUrlpattern = /.+(?=\/page\/\d*\/)/,
tagOrAuthorPattern = /\/(tag)|(author)\//;
function getNextUrl(data, absolute) {
var trimmedUrl, next;
if (data.relativeUrl) {
trimmedUrl = data.relativeUrl.match(trimmedUrlpattern);
if (data.pagination && data.pagination.next) {
next = '/page/' + data.pagination.next + '/';
if (trimmedUrl) {
next = trimmedUrl + next;
} else if (tagOrAuthorPattern.test(data.relativeUrl)) {
next = data.relativeUrl.slice(0, -1) + next;
}
return config.urlFor({relativeUrl: next, secure: data.secure}, absolute);
}
}
return null;
}
module.exports = getNextUrl;

View File

@ -0,0 +1,35 @@
var _ = require('lodash'),
config = require('../../config');
function getPaginatedUrl(page, data, absolute) {
// If we don't have enough information, return null right away
if (!data || !data.relativeUrl || !data.pagination) {
return null;
}
var pagePath = '/' + config.routeKeywords.page + '/',
// Try to match the base url, as whatever precedes the pagePath
baseUrlPattern = new RegExp('(.+)?(/' + config.routeKeywords.page + '/\\d+/)'),
baseUrlMatch = data.relativeUrl.match(baseUrlPattern),
// If there is no match for pagePath, use the original url, without the trailing slash
baseUrl = baseUrlMatch ? baseUrlMatch[1] : data.relativeUrl.slice(0, -1),
newRelativeUrl;
if (page === 'next' && data.pagination.next) {
newRelativeUrl = pagePath + data.pagination.next + '/';
} else if (page === 'prev' && data.pagination.prev) {
newRelativeUrl = data.pagination.prev > 1 ? pagePath + data.pagination.prev + '/' : '/';
} else if (_.isNumber(page)) {
newRelativeUrl = page > 1 ? pagePath + page + '/' : '/';
} else {
// If none of the cases match, return null right away
return null;
}
// baseUrl can be undefined, if there was nothing preceding the pagePath (e.g. first page of the index channel)
newRelativeUrl = baseUrl ? baseUrl + newRelativeUrl : newRelativeUrl;
return config.urlFor({relativeUrl: newRelativeUrl, secure: data.secure}, absolute);
}
module.exports = getPaginatedUrl;

View File

@ -1,18 +0,0 @@
var config = require('../../config'),
trimmedUrlpattern = /.+(?=\/page\/\d*\/)/;
function getPreviousUrl(data, absolute) {
var trimmedUrl, prev;
if (data.relativeUrl) {
trimmedUrl = data.relativeUrl.match(trimmedUrlpattern);
if (data.pagination && data.pagination.prev) {
prev = (data.pagination.prev > 1 ? '/page/' + data.pagination.prev + '/' : '/');
prev = (trimmedUrl) ? trimmedUrl + prev : prev;
return config.urlFor({relativeUrl: prev, secure: data.secure}, absolute);
}
}
return null;
}
module.exports = getPreviousUrl;

View File

@ -8,32 +8,14 @@
//
// We use the name page_url to match the helper for consistency:
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
var config = require('../config'),
errors = require('../errors'),
var errors = require('../errors'),
i18n = require('../i18n'),
getPaginatedUrl = require('../data/meta/paginated_url'),
page_url,
pageUrl;
page_url = function (pageNum, options) {
/*jshint unused:false*/
var url = config.paths.subdir;
if (this.tagSlug !== undefined) {
url += '/' + config.routeKeywords.tag + '/' + this.tagSlug;
}
if (this.authorSlug !== undefined) {
url += '/' + config.routeKeywords.author + '/' + this.authorSlug;
}
if (pageNum > 1) {
url += '/' + config.routeKeywords.page + '/' + pageNum;
}
url += '/';
return url;
page_url = function (page, options) {
return getPaginatedUrl(page, options.data.root);
};
// ### Page URL Helper: DEPRECATED

View File

@ -31,14 +31,6 @@ pagination = function (options) {
var data = _.merge({}, this.pagination);
if (this.tag !== undefined) {
data.tagSlug = this.tag.slug;
}
if (this.author !== undefined) {
data.authorSlug = this.author.slug;
}
return template.execute('pagination', data, options);
};

View File

@ -1,55 +0,0 @@
/*globals describe, it*/
var getNextUrl = require('../../../server/data/meta/next_url'),
should = require('should');
describe('getNextUrl', function () {
it('should return next url if relative url and pagination next set', function () {
var nextUrl = getNextUrl({
relativeUrl: '/test/page/2/',
pagination: {
next: '3'
}
});
should.equal(nextUrl, '/test/page/3/');
});
it('should return next url if with / relative url', function () {
var nextUrl = getNextUrl({
relativeUrl: '/',
pagination: {
next: '2'
}
});
should.equal(nextUrl, '/page/2/');
});
it('should return absolute next url if relative url and pagination next set', function () {
var nextUrl = getNextUrl({
relativeUrl: '/test/page/2/',
pagination: {
next: '3'
}
}, true);
should.equal(nextUrl, 'http://127.0.0.1:2369/test/page/3/');
});
it('should return null if no pagination next', function () {
var nextUrl = getNextUrl({
relativeUrl: '/',
pagination: {
prev: '2'
}
});
should.equal(nextUrl, null);
});
it('should return null if no relative url', function () {
var nextUrl = getNextUrl({
relativeUrl: '',
pagination: {
next: '2'
}
});
should.equal(nextUrl, null);
});
});

View File

@ -0,0 +1,168 @@
/*globals describe, it, before, beforeEach, after */
var should = require('should'),
getPaginatedUrl = require('../../../server/data/meta/paginated_url'),
configUtils = require('../../utils/configUtils');
describe('getPaginatedUrl', function () {
var data, getTestUrls;
beforeEach(function () {
data = {};
});
getTestUrls = function getTestUrls() {
return {
next: getPaginatedUrl('next', data, true),
prev: getPaginatedUrl('prev', data, true),
page1: getPaginatedUrl(1, data),
page5: getPaginatedUrl(5, data),
page10: getPaginatedUrl(10, data)
};
};
it('should be a function', function () {
should.exist(getPaginatedUrl);
});
describe('index', function () {
it('should calculate correct urls for the first page of the index channel', function () {
// Setup tests
data.relativeUrl = '/';
data.pagination = {prev: null, next: 2};
// Execute test
var urls = getTestUrls();
// Check results
urls.should.have.property('next', 'http://127.0.0.1:2369/page/2/');
urls.should.have.property('prev', null);
urls.should.have.property('page1', '/');
urls.should.have.property('page5', '/page/5/');
urls.should.have.property('page10', '/page/10/');
});
it('should calculate correct urls for the second page of the index channel', function () {
// Setup tests
data.relativeUrl = '/page/2/';
data.pagination = {prev: 1, next: 3};
// Execute test
var urls = getTestUrls();
// Check results
urls.should.have.property('next', 'http://127.0.0.1:2369/page/3/');
urls.should.have.property('prev', 'http://127.0.0.1:2369/');
urls.should.have.property('page1', '/');
urls.should.have.property('page5', '/page/5/');
urls.should.have.property('page10', '/page/10/');
});
it('should calculate correct urls for the last page of the index channel', function () {
// Setup tests
data.relativeUrl = '/page/10/';
data.pagination = {prev: 9, next: null};
// Execute test
var urls = getTestUrls();
// Check results
urls.should.have.property('next', null);
urls.should.have.property('prev', 'http://127.0.0.1:2369/page/9/');
urls.should.have.property('page1', '/');
urls.should.have.property('page5', '/page/5/');
urls.should.have.property('page10', '/page/10/');
});
});
describe('other', function () {
it('should calculate correct urls for the first page of another channel', function () {
// Setup tests
data.relativeUrl = '/featured/';
data.pagination = {prev: null, next: 2};
// Execute test
var urls = getTestUrls();
// Check results
urls.should.have.property('next', 'http://127.0.0.1:2369/featured/page/2/');
urls.should.have.property('prev', null);
urls.should.have.property('page1', '/featured/');
urls.should.have.property('page5', '/featured/page/5/');
urls.should.have.property('page10', '/featured/page/10/');
});
it('should calculate correct urls for the second page of another channel', function () {
// Setup tests
data.relativeUrl = '/featured/page/2/';
data.pagination = {prev: 1, next: 3};
// Execute test
var urls = getTestUrls();
// Check results
urls.should.have.property('next', 'http://127.0.0.1:2369/featured/page/3/');
urls.should.have.property('prev', 'http://127.0.0.1:2369/featured/');
urls.should.have.property('page1', '/featured/');
urls.should.have.property('page5', '/featured/page/5/');
urls.should.have.property('page10', '/featured/page/10/');
});
it('should calculate correct urls for the last page of another channel', function () {
// Setup tests
data.relativeUrl = '/featured/page/10/';
data.pagination = {prev: 9, next: null};
// Execute test
var urls = getTestUrls();
// Check results
urls.should.have.property('next', null);
urls.should.have.property('prev', 'http://127.0.0.1:2369/featured/page/9/');
urls.should.have.property('page1', '/featured/');
urls.should.have.property('page5', '/featured/page/5/');
urls.should.have.property('page10', '/featured/page/10/');
});
});
describe('with /blog subdirectory', function () {
before(function () {
configUtils.set({url: 'http://testurl.com/blog'});
});
after(function () {
configUtils.restore();
});
it('should calculate correct urls for index', function () {
// Setup tests
data.relativeUrl = '/page/2/';
data.pagination = {prev: 1, next: 3};
// Execute test
var urls = getTestUrls();
// Check results
urls.should.have.property('next', 'http://testurl.com/blog/page/3/');
urls.should.have.property('prev', 'http://testurl.com/blog/');
urls.should.have.property('page1', '/blog/');
urls.should.have.property('page5', '/blog/page/5/');
urls.should.have.property('page10', '/blog/page/10/');
});
it('should calculate correct urls for another channel', function () {
// Setup tests
data.relativeUrl = '/featured/page/2/';
data.pagination = {prev: 1, next: 3};
// Execute test
var urls = getTestUrls();
// Check results
urls.should.have.property('next', 'http://testurl.com/blog/featured/page/3/');
urls.should.have.property('prev', 'http://testurl.com/blog/featured/');
urls.should.have.property('page1', '/blog/featured/');
urls.should.have.property('page5', '/blog/featured/page/5/');
urls.should.have.property('page10', '/blog/featured/page/10/');
});
});
});

View File

@ -1,65 +0,0 @@
/*globals describe, it*/
var getPreviousUrl = require('../../../server/data/meta/previous_url'),
should = require('should');
describe('getPreviousUrl', function () {
it('should return prev url if relative url and pagination prev set', function () {
var prevUrl = getPreviousUrl({
relativeUrl: '/test/page/3/',
pagination: {
prev: '2'
}
});
should.equal(prevUrl, '/test/page/2/');
});
it('should return prev url if with / relative url', function () {
var prevUrl = getPreviousUrl({
relativeUrl: '/',
pagination: {
prev: '2'
}
});
should.equal(prevUrl, '/page/2/');
});
it('should return prev url with no page is prev is 1', function () {
var prevUrl = getPreviousUrl({
relativeUrl: '/page/2/',
pagination: {
prev: '1'
}
});
should.equal(prevUrl, '/');
});
it('should return absolute prev url if relative url and pagination prev set', function () {
var prevUrl = getPreviousUrl({
relativeUrl: '/test/page/2/',
pagination: {
prev: '3'
}
}, true);
should.equal(prevUrl, 'http://127.0.0.1:2369/test/page/3/');
});
it('should return null if no pagination prev', function () {
var prevUrl = getPreviousUrl({
relativeUrl: '/',
pagination: {
next: '2'
}
});
should.equal(prevUrl, null);
});
it('should return null if no relative url', function () {
var prevUrl = getPreviousUrl({
relativeUrl: '',
pagination: {
prev: '2'
}
});
should.equal(prevUrl, null);
});
});

View File

@ -1,82 +1,55 @@
/*globals describe, before, after, it*/
/*globals describe, before, beforeEach, it*/
var should = require('should'),
hbs = require('express-hbs'),
utils = require('./utils'),
configUtils = require('../../utils/configUtils'),
// Stuff we are testing
handlebars = hbs.handlebars,
helpers = require('../../../server/helpers');
handlebars = hbs.handlebars,
helpers = require('../../../server/helpers');
describe('{{page_url}} helper', function () {
var options = {data: {root: {pagination: {}}}};
before(function () {
utils.loadHelpers();
});
beforeEach(function () {
options.data.root = {pagination: {}};
});
it('has loaded page_url helper', function () {
should.exist(handlebars.helpers.page_url);
});
it('can return a valid url', function () {
helpers.page_url(1).should.equal('/');
helpers.page_url(2).should.equal('/page/2/');
helpers.page_url(50).should.equal('/page/50/');
it('can return a valid url when the relative URL is /', function () {
options.data.root.relativeUrl = '/';
options.data.root.pagination.next = 3;
options.data.root.pagination.prev = 6;
helpers.page_url(1, options).should.equal('/');
helpers.page_url(2, options).should.equal('/page/2/');
helpers.page_url(50, options).should.equal('/page/50/');
helpers.page_url('next', options).should.eql('/page/3/');
helpers.page_url('prev', options).should.eql('/page/6/');
});
it('can return a valid url for tag pages', function () {
var tagContext = {
tagSlug: 'pumpkin'
};
helpers.page_url.call(tagContext, 1).should.equal('/tag/pumpkin/');
helpers.page_url.call(tagContext, 2).should.equal('/tag/pumpkin/page/2/');
helpers.page_url.call(tagContext, 50).should.equal('/tag/pumpkin/page/50/');
});
it('can return a valid url when the relative url has a path', function () {
options.data.root.relativeUrl = '/tag/pumpkin/';
options.data.root.pagination.next = 10;
options.data.root.pagination.prev = 2;
it('can return a valid url for author pages', function () {
var authorContext = {
authorSlug: 'pumpkin'
};
helpers.page_url.call(authorContext, 1).should.equal('/author/pumpkin/');
helpers.page_url.call(authorContext, 2).should.equal('/author/pumpkin/page/2/');
helpers.page_url.call(authorContext, 50).should.equal('/author/pumpkin/page/50/');
});
describe('with /blog subdirectory', function () {
before(function () {
configUtils.set({url: 'http://testurl.com/blog'});
});
after(function () {
configUtils.restore();
});
it('can return a valid url with subdirectory', function () {
helpers.page_url(1).should.equal('/blog/');
helpers.page_url(2).should.equal('/blog/page/2/');
helpers.page_url(50).should.equal('/blog/page/50/');
});
it('can return a valid url for tag pages with subdirectory', function () {
var authorContext = {
authorSlug: 'pumpkin'
};
helpers.page_url.call(authorContext, 1).should.equal('/blog/author/pumpkin/');
helpers.page_url.call(authorContext, 2).should.equal('/blog/author/pumpkin/page/2/');
helpers.page_url.call(authorContext, 50).should.equal('/blog/author/pumpkin/page/50/');
});
it('can return a valid url for tag pages with subdirectory', function () {
var tagContext = {
tagSlug: 'pumpkin'
};
helpers.page_url.call(tagContext, 1).should.equal('/blog/tag/pumpkin/');
helpers.page_url.call(tagContext, 2).should.equal('/blog/tag/pumpkin/page/2/');
helpers.page_url.call(tagContext, 50).should.equal('/blog/tag/pumpkin/page/50/');
});
helpers.page_url(1, options).should.equal('/tag/pumpkin/');
helpers.page_url(2, options).should.equal('/tag/pumpkin/page/2/');
helpers.page_url(50, options).should.equal('/tag/pumpkin/page/50/');
helpers.page_url('next', options).should.eql('/tag/pumpkin/page/10/');
helpers.page_url('prev', options).should.eql('/tag/pumpkin/page/2/');
});
});
describe('{{pageUrl}} helper [DEPRECATED]', function () {
var options = {data: {root: {pagination: {}}}};
before(function () {
utils.loadHelpers();
});
@ -85,61 +58,25 @@ describe('{{pageUrl}} helper [DEPRECATED]', function () {
should.exist(handlebars.helpers.pageUrl);
});
it('can return a valid url', function () {
helpers.pageUrl(1).should.equal('/');
helpers.pageUrl(2).should.equal('/page/2/');
helpers.pageUrl(50).should.equal('/page/50/');
it('should do the same thing as page_url when the relative URL is /', function () {
options.data.root.relativeUrl = '/';
options.data.root.pagination.next = 5;
options.data.root.pagination.prev = 7;
helpers.pageUrl(1, options).should.eql(helpers.page_url(1, options));
helpers.pageUrl(20, options).should.eql(helpers.page_url(20, options));
helpers.pageUrl('next', options).should.eql(helpers.page_url('next', options));
helpers.pageUrl('prev', options).should.eql(helpers.page_url('prev', options));
});
it('can return a valid url for author pages', function () {
var authorContext = {
authorSlug: 'pumpkin'
};
helpers.pageUrl.call(authorContext, 1).should.equal('/author/pumpkin/');
helpers.pageUrl.call(authorContext, 2).should.equal('/author/pumpkin/page/2/');
helpers.pageUrl.call(authorContext, 50).should.equal('/author/pumpkin/page/50/');
});
it('should do the same thing as page_url when the relative url has a path', function () {
options.data.root.relativeUrl = '/tag/pumpkin/';
options.data.root.pagination.next = 12;
options.data.root.pagination.prev = 9;
it('can return a valid url for tag pages', function () {
var tagContext = {
tagSlug: 'pumpkin'
};
helpers.pageUrl.call(tagContext, 1).should.equal('/tag/pumpkin/');
helpers.pageUrl.call(tagContext, 2).should.equal('/tag/pumpkin/page/2/');
helpers.pageUrl.call(tagContext, 50).should.equal('/tag/pumpkin/page/50/');
});
describe('with /blog subdirectory', function () {
before(function () {
configUtils.set({url: 'http://testurl.com/blog'});
});
after(function () {
configUtils.restore();
});
it('can return a valid url with subdirectory', function () {
helpers.pageUrl(1).should.equal('/blog/');
helpers.pageUrl(2).should.equal('/blog/page/2/');
helpers.pageUrl(50).should.equal('/blog/page/50/');
});
it('can return a valid url for tag pages with subdirectory', function () {
var tagContext = {
tagSlug: 'pumpkin'
};
helpers.pageUrl.call(tagContext, 1).should.equal('/blog/tag/pumpkin/');
helpers.pageUrl.call(tagContext, 2).should.equal('/blog/tag/pumpkin/page/2/');
helpers.pageUrl.call(tagContext, 50).should.equal('/blog/tag/pumpkin/page/50/');
});
it('can return a valid url for tag pages with subdirectory', function () {
var tagContext = {
tagSlug: 'pumpkin'
};
helpers.pageUrl.call(tagContext, 1).should.equal('/blog/tag/pumpkin/');
helpers.pageUrl.call(tagContext, 2).should.equal('/blog/tag/pumpkin/page/2/');
helpers.pageUrl.call(tagContext, 50).should.equal('/blog/tag/pumpkin/page/50/');
});
helpers.pageUrl(1, options).should.eql(helpers.page_url(1, options));
helpers.pageUrl(20, options).should.eql(helpers.page_url(20, options));
helpers.pageUrl('next', options).should.eql(helpers.page_url('next', options));
helpers.pageUrl('prev', options).should.eql(helpers.page_url('prev', options));
});
});