Skip to content
This repository has been archived by the owner on Dec 5, 2022. It is now read-only.

Commit

Permalink
feat(project-partial): add new feature
Browse files Browse the repository at this point in the history
test(project-partial): add more unit test
	* Try using golang table testing approach
	* coverage 100%

docs(project-partials): update comments

close #25
  • Loading branch information
rbarilani committed Nov 16, 2017
1 parent 127f628 commit a98c6dd
Show file tree
Hide file tree
Showing 5 changed files with 247 additions and 0 deletions.
8 changes: 8 additions & 0 deletions layout/layout.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
<meta name="description" content="<%- page.description %>">
<meta name="viewport" content="width=device-width, initial-scale=1">

<%- project_partial('head_start') %>

<!-- fonts -->
<link href="//fonts.googleapis.com/css?family=Source+Sans+Pro:400,700" rel="stylesheet">
<link href="//fonts.googleapis.com/css?family=Ubuntu:300,400,500,600,700" rel="stylesheet">
Expand All @@ -17,6 +19,8 @@
<!-- favicon -->
<link rel="icon" href="<%- url_for(config.theme_config.favicon) %>">

<%- project_partial('head_end') %>

</head>
<body>

Expand All @@ -26,12 +30,16 @@
<%- body %>
<%- partial('_partial/google_analytics') %>

<%- project_partial('footer_start') %>

<!-- js vendors -->
<script src="//code.jquery.com/jquery-3.2.1.min.js" crossorigin="anonymous"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/lunr.js/2.1.0/lunr.min.js"></script>

<!-- js source -->
<script src="<%- url_for('script/doc.js') %>"></script>

<%- project_partial('footer_end') %>

</body>
</html>
135 changes: 135 additions & 0 deletions lib/nodejs/project-partial/__tests__/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
'use strict';

const {getPartials, createHelper} = require('../index');
const {merge} = require('lodash');

const mockLogger = {
debug: jest.fn(),
info () {},
warn () {},
error: jest.fn()
};

describe('project-partial.createHelper', () => {
const defaultScenario = {
source_dir: '/root',
log: mockLogger,
render: {
isRenderable: () => true,
getOutput: () => 'html',
renderSync: jest.fn().mockReturnValue('foo')
},
theme_config: {
partials: {
head_start: './foo.ejs'
}
},
area: 'head_start'
};

const scenarios = [{
message: 'should render one partial for head_start area',
expected: {
helper_output: 'foo'
}
},
{
message: 'should render multiple partials for head_start area',
theme_config: {
partials: {
head_start: ['./foo.ejs', './foo.ejs', './foo.ejs']
}
},
expected: {
helper_output: 'foo\nfoo\nfoo'
}
}, {
message: 'should render empty string because error occured while rendering',
render: {
renderSync: () => { throw new Error('foo'); }
},
expected: {
error: true,
helper_output: ''
}
},{
message: 'should render empty string because area doesn\'t exist',
area: 'foo_bar',
expected: {
helper_output: ''
}
}];

scenarios.map((scenario) => {
const s = merge({}, defaultScenario, scenario);
s.log.error.mockClear();
const helper = createHelper(s);

it(s.message, () => {
expect(s.log.error).not.toHaveBeenCalled();
expect(typeof helper).toEqual('function');
expect(helper(s.area)).toEqual(s.expected.helper_output);
expect(helper(s.area)).toEqual(s.expected.helper_output);

if (s.expected.error) {
expect(s.log.error).toHaveBeenCalled();
}
});
});
});

describe('project-partial.getPartials', () => {
const defaultScenario = {
source_dir: '/root',
log: mockLogger,
};

const scenarios = [{
render: {
isRenderable: () => true,
getOutput: () => 'html'
},
theme_config: {
partials: {
head_start: './foo.ejs'
}
},
expected: {
head_start: ['/root/foo.ejs']
}
},
{
render: {
isRenderable: () => true,
getOutput: () => 'jpeg'
},
theme_config: {
partials: {
head_start: './foo.ejs'
}
},
expected: {
head_start: []
}
}];

scenarios.map((scenario) => {
const s = Object.assign({}, defaultScenario, scenario);
const partials = getPartials(s);

it('should return array for every area', () => {
Object.keys(partials).forEach((area) => {
const p = partials[area];
expect(Array.isArray(p)).toBe(true);
});
});

it('should transform a value into an array with one element', () => {
expect(partials.head_start.length).toEqual(s.expected.head_start.length);
});

it('should resolve partials\' path relative to project source dir', () => {
expect(partials.head_start).toEqual(s.expected.head_start);
});
});
});
89 changes: 89 additions & 0 deletions lib/nodejs/project-partial/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
'use strict';

const path = require('path');

function createHelper ({theme_config, source_dir, render, log}) {

let partials = null;

function projectPartialHelper (area) {

// Get and normalize "partials" configuration once and cache the result
//
// We do this operation inside the helper function because:
//
// - it means that `project_partial` template helper is really used in the layout
// - the rendering process is involved (it will not be excuted if you run `hexo clean`, for example)

if (!partials) {
partials = getPartials({
theme_config, source_dir, render, log
});
}

if (!partials[area]) { return ''; }

// in a hexo helper plugin function,
// "this" represents the same context that hexo pass
// when rendering theme's layout partials
const locals = this;
return partials[area]
.map((p) => renderPartial(p, locals))
.filter(h => h)
.join('\n');
}

function renderPartial (partialPath, locals) {
try {
return render.renderSync({ path: partialPath }, locals);
} catch (err) {
log.error(`There was a problem while rendering partial "${partialPath}". skip rendering.`);
log.error(`${err.message}`);
log.debug(err);
return '';
}
}

return projectPartialHelper;
}

function getPartials ({theme_config, source_dir, render, log}) {
const partials = Object.assign({
head_start: [],
head_end: [],
footer_start: [],
footer_end: []
}, theme_config.partials);

const isValidPartial = (p) => {
return typeof p === 'string';
};

const isRenderablePartial = (p) => {
if (!render.isRenderable(p) || render.getOutput(p) !== 'html') {
log.warn(`partial "${p}" cannot be rendered or the output is not html.`);
return false;
}
return true;
};

// "normalize" partials for each area:
// - always use array type for each area
// - exclude invalid partials or non renderable partials
return Object.keys(partials).reduce((normalizedPartials, area) => {

if (!Array.isArray(partials[area])) {
partials[area] = [partials[area]];
}

normalizedPartials[area] = partials[area]
.filter(isValidPartial)
.map((p) => path.resolve(source_dir, p))
.filter(isRenderablePartial);

return normalizedPartials;

}, {});
}

module.exports = { getPartials, createHelper };
14 changes: 14 additions & 0 deletions plugins/project-partial.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use strict';

const {createHelper} = require('../lib/nodejs/project-partial');

module.exports = ({hexo}) => {

hexo.extend.helper.register('project_partial', createHelper({
theme_config: hexo.config.theme_config,
source_dir: hexo.source_dir,
render: hexo.render,
log: hexo.log
}));

};
1 change: 1 addition & 0 deletions scripts/all.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/* global hexo */

require('../plugins/favicon')({hexo});
require('../plugins/project-partial')({hexo});
require('../plugins/search')({hexo});
require('../plugins/swagger-to-html')({hexo});
require('../plugins/swagger-ui')({hexo});
Expand Down

0 comments on commit a98c6dd

Please sign in to comment.