var should = require('should'), hbs = require('../../../server/services/themes/engine'), configUtils = require('../../utils/configUtils'), path = require('path'), helpers = require('../../../server/helpers'); describe('{{navigation}} helper', function () { var runHelper = function (data) { return function () { helpers.navigation(data); }; }, optionsData; before(function (done) { hbs.express3({ partialsDir: [configUtils.config.get('paths').helperTemplates] }); hbs.cachePartials(function () { done(); }); // The navigation partial expects this helper // @TODO: change to register with Ghost's own registration tools hbs.registerHelper('url', helpers.url); }); beforeEach(function () { optionsData = { data: { blog: { navigation: [] }, root: { relativeUrl: '' } } }; }); it('should throw errors on invalid data', function () { // Test 1: navigation = string optionsData.data.blog.navigation = 'not an object'; runHelper(optionsData).should.throwError('navigation data is not an object or is a function'); // Test 2: navigation = function optionsData.data.blog.navigation = function () { }; runHelper(optionsData).should.throwError('navigation data is not an object or is a function'); // Test 3: invalid label optionsData.data.blog.navigation = [{label: 1, url: 'bar'}]; runHelper(optionsData).should.throwError('Invalid value, Url and Label must be strings'); // Test 4: invalid url optionsData.data.blog.navigation = [{label: 'foo', url: 1}]; runHelper(optionsData).should.throwError('Invalid value, Url and Label must be strings'); }); it('can render empty nav', function () { var rendered = helpers.navigation(optionsData); should.exist(rendered); rendered.string.should.be.equal(''); }); it('can handle relativeUrl not being set (e.g. for images/assets)', function () { var singleItem = {label: 'Foo', url: '/foo'}, rendered; delete optionsData.data.root.relativeUrl; optionsData.data.blog.navigation = [singleItem]; rendered = helpers.navigation(optionsData); rendered.string.should.containEql('li'); rendered.string.should.containEql('nav-foo'); rendered.string.should.containEql('/foo'); }); it('can render one item', function () { var singleItem = {label: 'Foo', url: '/foo'}, testUrl = 'href="' + configUtils.config.get('url') + '/foo"', rendered; optionsData.data.blog.navigation = [singleItem]; rendered = helpers.navigation(optionsData); should.exist(rendered); rendered.string.should.containEql('li'); rendered.string.should.containEql('nav-foo'); rendered.string.should.containEql(testUrl); }); it('can render multiple items', function () { var firstItem = {label: 'Foo', url: '/foo'}, secondItem = {label: 'Bar Baz Qux', url: '/qux'}, testUrl = 'href="' + configUtils.config.get('url') + '/foo"', testUrl2 = 'href="' + configUtils.config.get('url') + '/qux"', rendered; optionsData.data.blog.navigation = [firstItem, secondItem]; rendered = helpers.navigation(optionsData); should.exist(rendered); rendered.string.should.containEql('nav-foo'); rendered.string.should.containEql('nav-bar-baz-qux'); rendered.string.should.containEql(testUrl); rendered.string.should.containEql(testUrl2); }); it('can annotate the current url', function () { var firstItem = {label: 'Foo', url: '/foo'}, secondItem = {label: 'Bar', url: '/qux'}, rendered; optionsData.data.blog.navigation = [firstItem, secondItem]; optionsData.data.root.relativeUrl = '/foo'; rendered = helpers.navigation(optionsData); should.exist(rendered); rendered.string.should.containEql('nav-foo'); rendered.string.should.containEql('nav-current'); rendered.string.should.containEql('nav-foo nav-current'); rendered.string.should.containEql('nav-bar"'); }); it('can annotate current url with trailing slash', function () { var firstItem = {label: 'Foo', url: '/foo'}, secondItem = {label: 'Bar', url: '/qux'}, rendered; optionsData.data.blog.navigation = [firstItem, secondItem]; optionsData.data.root.relativeUrl = '/foo/'; rendered = helpers.navigation(optionsData); should.exist(rendered); rendered.string.should.containEql('nav-foo'); rendered.string.should.containEql('nav-current'); rendered.string.should.containEql('nav-foo nav-current'); rendered.string.should.containEql('nav-bar"'); }); it('doesn\'t html-escape URLs', function () { var firstItem = {label: 'Foo', url: '/?foo=bar&baz=qux'}, rendered; optionsData.data.blog.navigation = [firstItem]; rendered = helpers.navigation(optionsData); should.exist(rendered); rendered.string.should.not.containEql('='); rendered.string.should.not.containEql('&'); rendered.string.should.containEql('/?foo=bar&baz=qux'); }); it('encodes URLs', function () { var firstItem = {label: 'Foo', url: '/?foo=space bar&'}, rendered; optionsData.data.blog.navigation = [firstItem]; rendered = helpers.navigation(optionsData); should.exist(rendered); rendered.string.should.containEql('foo=space%20bar'); rendered.string.should.not.containEql(''); rendered.string.should.containEql('%3Cscript%3Ealert(%22gotcha%22)%3C/script%3E'); }); it('doesn\'t double-encode URLs', function () { var firstItem = {label: 'Foo', url: '/?foo=space%20bar'}, rendered; optionsData.data.blog.navigation = [firstItem]; rendered = helpers.navigation(optionsData); should.exist(rendered); rendered.string.should.not.containEql('foo=space%2520bar'); }); }); describe('{{navigation}} helper with custom template', function () { var optionsData; before(function (done) { hbs.express3({ partialsDir: [path.resolve(configUtils.config.get('paths').corePath, 'test/unit/helpers/test_tpl')] }); hbs.cachePartials(function () { done(); }); }); beforeEach(function () { optionsData = { data: { blog: { navigation: [], title: 'Chaos is a ladder.' }, root: { relativeUrl: '' } } }; }); it('can render one item and @blog title', function () { var singleItem = {label: 'Foo', url: '/foo'}, testUrl = 'href="' + configUtils.config.get('url') + '/foo"', rendered; optionsData.data.blog.navigation = [singleItem]; rendered = helpers.navigation(optionsData); should.exist(rendered); rendered.string.should.containEql('Chaos is a ladder'); rendered.string.should.containEql(testUrl); rendered.string.should.containEql('Foo'); }); });