diff --git a/.github/ISSUE_TEMPLATE/other.md b/.github/ISSUE_TEMPLATE/other.md
index a59e50ad6a..6d0d667285 100644
--- a/.github/ISSUE_TEMPLATE/other.md
+++ b/.github/ISSUE_TEMPLATE/other.md
@@ -24,6 +24,8 @@ Please take extra precaution not to attach any secret environment variables (lik
Please check followings before submitting a new issue.
- [ ] I have already confirmed other issue template and they are not different my want
+- [ ] Not a question, feature-request, bug
+ - Please submit to [discussion](https://github.com/hexojs/hexo/discussions) if your issue is a question.
## Information
diff --git a/.github/workflows/commenter.yml b/.github/workflows/commenter.yml
index 480f01b595..f449809a30 100644
--- a/.github/workflows/commenter.yml
+++ b/.github/workflows/commenter.yml
@@ -2,8 +2,13 @@ name: Commenter
on: [pull_request_target]
+permissions:
+ contents: read
+
jobs:
commenter:
+ permissions:
+ pull-requests: write # for marocchino/sticky-pull-request-comment to create or update PR comment
runs-on: ubuntu-latest
steps:
- name: Comment PR
diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml
index 6273035b57..d3d9678043 100644
--- a/.github/workflows/linter.yml
+++ b/.github/workflows/linter.yml
@@ -2,6 +2,9 @@ name: Linter
on: [push, pull_request]
+permissions:
+ contents: read
+
jobs:
linter:
runs-on: ubuntu-latest
diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml
index 17fdb961dd..d1955effff 100644
--- a/.github/workflows/release-drafter.yml
+++ b/.github/workflows/release-drafter.yml
@@ -5,8 +5,14 @@ on:
branches:
- master
+permissions:
+ contents: read
+
jobs:
update_release_draft:
+ permissions:
+ contents: write # for release-drafter/release-drafter to create a github release
+ pull-requests: write # for release-drafter/release-drafter to add label to PR
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@v5
diff --git a/.github/workflows/tester.yml b/.github/workflows/tester.yml
index 64f7130856..e94651c609 100644
--- a/.github/workflows/tester.yml
+++ b/.github/workflows/tester.yml
@@ -2,6 +2,9 @@ name: Tester
on: [push, pull_request]
+permissions:
+ contents: read
+
jobs:
tester:
runs-on: ${{ matrix.os }}
@@ -23,6 +26,9 @@ jobs:
env:
CI: true
coverage:
+ permissions:
+ checks: write # for coverallsapp/github-action to create new checks
+ contents: read # for actions/checkout to fetch code
runs-on: ${{ matrix.os }}
strategy:
matrix:
diff --git a/lib/extend/tag.js b/lib/extend/tag.js
index 4334240278..f633781513 100644
--- a/lib/extend/tag.js
+++ b/lib/extend/tag.js
@@ -160,13 +160,15 @@ const getContext = (lines, errLine, location, type) => {
* @return {Error} New error object with embedded context
*/
const formatNunjucksError = (err, input, source = '') => {
+ err.message = err.message.replace('(unknown path)', source ? magenta(source) : '');
+
const match = err.message.match(/Line (\d+), Column \d+/);
if (!match) return err;
const errLine = parseInt(match[1], 10);
if (isNaN(errLine)) return err;
// trim useless info from Nunjucks Error
- const splited = err.message.replace('(unknown path)', source ? magenta(source) : '').split('\n');
+ const splited = err.message.split('\n');
const e = new Error();
e.name = 'Nunjucks Error';
@@ -243,7 +245,9 @@ class Tag {
options,
cb
);
- }).catch(err => Promise.reject(formatNunjucksError(err, str, source)))
+ }).catch(err => {
+ return Promise.reject(formatNunjucksError(err, str, source));
+ })
.asCallback(callback);
}
}
diff --git a/lib/hexo/index.js b/lib/hexo/index.js
index 332080396f..30a1f78956 100644
--- a/lib/hexo/index.js
+++ b/lib/hexo/index.js
@@ -151,7 +151,7 @@ class Hexo extends EventEmitter {
const dbPath = args.output || base;
if (/^(init|new|g|publish|s|deploy|render|migrate)/.test(this.env.cmd)) {
- this.log.d(`Writing database to ${dbPath}/db.json`);
+ this.log.d(`Writing database to ${join(dbPath, 'db.json')}`);
}
this.database = new Database({
diff --git a/lib/plugins/helper/index.js b/lib/plugins/helper/index.js
index c7dc22b531..fc86aa6074 100644
--- a/lib/plugins/helper/index.js
+++ b/lib/plugins/helper/index.js
@@ -31,6 +31,7 @@ module.exports = ctx => {
const is = require('./is');
helper.register('is_current', is.current);
helper.register('is_home', is.home);
+ helper.register('is_home_first_page', is.home_first_page);
helper.register('is_post', is.post);
helper.register('is_page', is.page);
helper.register('is_archive', is.archive);
diff --git a/lib/plugins/helper/is.js b/lib/plugins/helper/is.js
index 2724428aef..9f74f3b6d6 100644
--- a/lib/plugins/helper/is.js
+++ b/lib/plugins/helper/is.js
@@ -23,6 +23,10 @@ function isHomeHelper() {
return Boolean(this.page.__index);
}
+function isHomeFirstPageHelper() {
+ return Boolean(this.page.__index) && this.page.current === 1;
+}
+
function isPostHelper() {
return Boolean(this.page.__post);
}
@@ -79,6 +83,7 @@ function isTagHelper(tag) {
exports.current = isCurrentHelper;
exports.home = isHomeHelper;
+exports.home_first_page = isHomeFirstPageHelper;
exports.post = isPostHelper;
exports.page = isPageHelper;
exports.archive = isArchiveHelper;
diff --git a/lib/plugins/helper/mail_to.js b/lib/plugins/helper/mail_to.js
index 8eb51a8944..9752f0055d 100644
--- a/lib/plugins/helper/mail_to.js
+++ b/lib/plugins/helper/mail_to.js
@@ -1,7 +1,6 @@
'use strict';
const { htmlTag } = require('hexo-util');
-const qs = require('querystring');
const { default: moize } = require('moize');
function mailToHelper(path, text, options = {}) {
@@ -28,7 +27,7 @@ function mailToHelper(path, text, options = {}) {
}
});
- const querystring = qs.stringify(data);
+ const querystring = new URLSearchParams(data).toString();
if (querystring) attrs.href += `?${querystring}`;
return htmlTag('a', attrs, text);
diff --git a/lib/plugins/helper/open_graph.js b/lib/plugins/helper/open_graph.js
index 9506bb00e3..35d8beb12e 100644
--- a/lib/plugins/helper/open_graph.js
+++ b/lib/plugins/helper/open_graph.js
@@ -1,6 +1,5 @@
'use strict';
-const { parse, resolve } = require('url');
const { isMoment, isDate } = require('moment');
const { encodeURL, prettyUrls, htmlTag, stripHTML, escapeHTML } = require('hexo-util');
const { default: moize } = require('moize');
@@ -56,7 +55,6 @@ const og = (name, content, escape) => {
};
function openGraphHelper(options = {}) {
-
const { config, page } = this;
const { content } = page;
let images = options.image || options.images || page.photos || [];
@@ -117,15 +115,7 @@ function openGraphHelper(options = {}) {
result += og('og:locale', localeToTerritory(language), false);
}
- images = images.map(path => {
- if (!parse(path).host) {
- // resolve `path`'s absolute path relative to current page's url
- // `path` can be both absolute (starts with `/`) or relative.
- return resolve(url || config.url, path);
- }
-
- return path;
- });
+ images = images.map(path => new URL(path, url || config.url).toString());
images.forEach(path => {
result += og('og:image', path, false);
@@ -161,9 +151,7 @@ function openGraphHelper(options = {}) {
if (options.twitter_image) {
let twitter_image = options.twitter_image;
- if (!parse(twitter_image).host) {
- twitter_image = resolve(url || config.url, twitter_image);
- }
+ twitter_image = new URL(twitter_image, url || config.url);
result += meta('twitter:image', twitter_image, false);
} else if (images.length) {
result += meta('twitter:image', images[0], false);
diff --git a/lib/plugins/helper/paginator.js b/lib/plugins/helper/paginator.js
index 7dbff7d8c8..662c61022c 100644
--- a/lib/plugins/helper/paginator.js
+++ b/lib/plugins/helper/paginator.js
@@ -10,13 +10,19 @@ const createLink = (options, ctx) => {
const createPageTag = (options, ctx) => {
const link = createLink(options, ctx);
- const { current, escape, transform } = options;
+ const {
+ current,
+ escape,
+ transform,
+ page_class: pageClass,
+ current_class: currentClass
+ } = options;
return i => {
if (i === current) {
- return htmlTag('span', { class: 'page-number current' }, transform ? transform(i) : i, escape);
+ return htmlTag('span', { class: pageClass + ' ' + currentClass }, transform ? transform(i) : i, escape);
}
- return htmlTag('a', { class: 'page-number', href: link(i) }, transform ? transform(i) : i, escape);
+ return htmlTag('a', { class: pageClass, href: link(i) }, transform ? transform(i) : i, escape);
};
};
@@ -36,14 +42,15 @@ const pagenasionPartShow = (tags, options, ctx) => {
total,
space,
end_size: endSize,
- mid_size: midSize
+ mid_size: midSize,
+ space_class: spaceClass
} = options;
const leftEnd = Math.min(endSize, current - 1);
const rightEnd = Math.max(total - endSize + 1, current + 1);
const leftMid = Math.max(leftEnd + 1, current - midSize);
const rightMid = Math.min(rightEnd - 1, current + midSize);
- const spaceHtml = htmlTag('span', { class: 'space' }, space, false);
+ const spaceHtml = htmlTag('span', { class: spaceClass }, space, false);
const pageTag = createPageTag(options, ctx);
@@ -93,7 +100,13 @@ function paginatorHelper(options = {}) {
next_text: 'Next',
prev_text: 'Prev',
prev_next: true,
- escape: true
+ escape: true,
+ page_class: 'page-number',
+ current_class: 'current',
+ space_class: 'space',
+ prev_class: 'extend prev',
+ next_class: 'extend next',
+ force_prev_next: false
}, options);
const {
@@ -102,7 +115,10 @@ function paginatorHelper(options = {}) {
prev_text: prevText,
next_text: nextText,
prev_next: prevNext,
- escape
+ escape,
+ prev_class: prevClass,
+ next_class: nextClass,
+ force_prev_next: forcePrevNext
} = options;
if (!current) return '';
@@ -113,7 +129,9 @@ function paginatorHelper(options = {}) {
// Display the link to the previous page
if (prevNext && current > 1) {
- tags.push(htmlTag('a', { class: 'extend prev', rel: 'prev', href: link(current - 1)}, prevText, escape));
+ tags.push(htmlTag('a', { class: prevClass, rel: 'prev', href: link(current - 1)}, prevText, escape));
+ } else if (forcePrevNext) {
+ tags.push(htmlTag('span', { class: prevClass, rel: 'prev' }, prevText, escape));
}
if (options.show_all) {
@@ -124,7 +142,9 @@ function paginatorHelper(options = {}) {
// Display the link to the next page
if (prevNext && current < total) {
- tags.push(htmlTag('a', { class: 'extend next', rel: 'next', href: link(current + 1) }, nextText, escape));
+ tags.push(htmlTag('a', { class: nextClass, rel: 'next', href: link(current + 1) }, nextText, escape));
+ } else if (forcePrevNext) {
+ tags.push(htmlTag('span', { class: nextClass, rel: 'next' }, nextText, escape));
}
return tags.join('');
diff --git a/lib/plugins/helper/toc.js b/lib/plugins/helper/toc.js
index e6e4fa8c7b..e055ffd6f2 100644
--- a/lib/plugins/helper/toc.js
+++ b/lib/plugins/helper/toc.js
@@ -7,6 +7,12 @@ function tocHelper(str, options = {}) {
min_depth: 1,
max_depth: 6,
class: 'toc',
+ class_item: '',
+ class_link: '',
+ class_text: '',
+ class_child: '',
+ class_number: '',
+ class_level: '',
list_number: true
}, options);
@@ -15,6 +21,12 @@ function tocHelper(str, options = {}) {
if (!data.length) return '';
const className = escapeHTML(options.class);
+ const itemClassName = escapeHTML(options.class_item || options.class + '-item');
+ const linkClassName = escapeHTML(options.class_link || options.class + '-link');
+ const textClassName = escapeHTML(options.class_text || options.class + '-text');
+ const childClassName = escapeHTML(options.class_child || options.class + '-child');
+ const numberClassName = escapeHTML(options.class_number || options.class + '-number');
+ const levelClassName = escapeHTML(options.class_level || options.class + '-level');
const listNumber = options.list_number;
let result = `
`;
@@ -42,7 +54,7 @@ function tocHelper(str, options = {}) {
}
if (level > lastLevel) {
- result += ``;
+ result += ``;
} else {
result += '';
}
@@ -50,15 +62,15 @@ function tocHelper(str, options = {}) {
firstLevel = level;
}
- result += `- `;
+ result += `
- `;
if (href) {
- result += ``;
+ result += ``;
} else {
- result += ``;
+ result += ``;
}
if (listNumber && !el.unnumbered) {
- result += ``;
+ result += ``;
for (let i = firstLevel - 1; i < level; i++) {
result += `${lastNumber[i]}.`;
@@ -67,7 +79,7 @@ function tocHelper(str, options = {}) {
result += ' ';
}
- result += `${text}`;
+ result += `${text}`;
lastLevel = level;
}
diff --git a/lib/plugins/tag/include_code.js b/lib/plugins/tag/include_code.js
index 59e4436a16..a7cd9f6c3a 100644
--- a/lib/plugins/tag/include_code.js
+++ b/lib/plugins/tag/include_code.js
@@ -1,7 +1,7 @@
'use strict';
const { exists, readFile } = require('hexo-fs');
-const { basename, extname, join } = require('path');
+const { basename, extname, join, posix } = require('path');
// Lazy require highlight.js & prismjs
let highlight, prismHighlight;
@@ -55,7 +55,7 @@ module.exports = ctx => function includeCodeTag(args) {
// If the title is not defined, use file name instead
const title = match[1] || basename(path);
- const caption = `${title}view raw`;
+ const caption = `${title}view raw`;
const hljsCfg = ctx.config.highlight || {};
const prismjsCfg = ctx.config.prismjs || {};
diff --git a/lib/plugins/tag/post_link.js b/lib/plugins/tag/post_link.js
index f27cec0c4e..d70ada3726 100644
--- a/lib/plugins/tag/post_link.js
+++ b/lib/plugins/tag/post_link.js
@@ -12,9 +12,10 @@ const { postFindOneFactory } = require('./');
*/
module.exports = ctx => {
return function postLinkTag(args) {
- const error = `Post not found: ${args.join(' ') || 'Invalid post_link'}`;
const slug = args.shift();
- if (!slug) return error;
+ if (!slug) {
+ throw new Error(`Post not found: "${slug}" doesn't exist for {% post_link %}`);
+ }
let escape = args[args.length - 1];
if (escape === 'true' || escape === 'false') {
@@ -24,7 +25,9 @@ module.exports = ctx => {
}
const post = postFindOneFactory(ctx)({ slug });
- if (!post) return error;
+ if (!post) {
+ throw new Error(`Post not found: post_link ${slug}.`);
+ }
let title = args.length ? args.join(' ') : post.title;
const attrTitle = escapeHTML(title);
diff --git a/package.json b/package.json
index c0eca988fe..095b66a153 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,7 @@
"scripts": {
"eslint": "eslint .",
"test": "mocha test/index.js",
- "test-cov": "nyc --reporter=lcovonly npm test -- --no-parallel",
+ "test-cov": "c8 --reporter=lcovonly npm test -- --no-parallel",
"prepare": "husky install"
},
"directories": {
@@ -47,27 +47,28 @@
"hexo-fs": "^3.1.0",
"hexo-i18n": "^1.0.0",
"hexo-log": "^3.0.0",
- "hexo-util": "^2.6.1",
+ "hexo-util": "^2.7.0",
"js-yaml": "^4.1.0",
"js-yaml-js-types": "^1.0.0",
"micromatch": "^4.0.4",
"moize": "^6.1.0",
"moment": "^2.29.1",
"moment-timezone": "^0.5.34",
- "picocolors": "^1.0.0",
"nunjucks": "^3.2.3",
"parse5": "^7.0.0",
+ "picocolors": "^1.0.0",
"pretty-hrtime": "^1.0.3",
"resolve": "^1.22.0",
"strip-ansi": "^6.0.0",
"text-table": "^0.2.0",
"tildify": "^2.0.0",
"titlecase": "^1.1.3",
- "warehouse": "^4.0.1"
+ "warehouse": "^4.0.2"
},
"devDependencies": {
"@easyops/git-exec-and-restage": "^1.0.4",
"0x": "^5.1.2",
+ "c8": "^7.12.0",
"chai": "^4.3.6",
"cheerio": "0.22.0",
"decache": "^4.6.1",
@@ -77,7 +78,6 @@
"husky": "^7.0.4",
"lint-staged": "^11.0.0",
"mocha": "^10.0.0",
- "nyc": "^15.1.0",
"sinon": "^13.0.2"
},
"engines": {
diff --git a/test/benchmark.js b/test/benchmark.js
index 5993632f6d..1dad3c008d 100644
--- a/test/benchmark.js
+++ b/test/benchmark.js
@@ -4,6 +4,7 @@ const { performance, PerformanceObserver } = require('perf_hooks');
const { spawn } = require('child_process');
const { spawn: spawnAsync } = require('hexo-util');
const { rmdir, exists } = require('hexo-fs');
+const { appendFileSync: appendFile } = require('fs');
const { join, resolve } = require('path');
const log = require('hexo-log')();
const { red } = require('picocolors');
@@ -26,6 +27,8 @@ const zeroEksDir = process.env.TRAVIS_BUILD_DIR
: resolve(testDir, '0x');
const hexoBin = resolve(testDir, 'node_modules/.bin/hexo');
+const isGitHubActions = process.env.GITHUB_ACTIONS;
+
const zeroEks = require('0x');
let isProfiling = process.argv.join(' ').includes('--profiling');
@@ -41,6 +44,12 @@ if (!isProfiling && !isBenchmark) {
if (isBenchmark) {
log.info('Running benchmark');
+
+ if (isGitHubActions) {
+ log.info('Running in GitHub Actions.');
+ appendFile(process.env.GITHUB_STEP_SUMMARY, '# Benchmark Result\n');
+ }
+
await cleanUp();
await run_benchmark('Cold processing');
await run_benchmark('Hot processing');
@@ -76,8 +85,14 @@ async function run_benchmark(name) {
if (measureFinished) {
obs.disconnect();
+
+ if (isGitHubActions) {
+ appendFile(process.env.GITHUB_STEP_SUMMARY, `\n## ${name}\n\n| Step | Cost time (s) |\n| --- | --- |\n${Object.keys(result).map(name => `| ${name} | ${result[name]['Cost time (s)']} |`).join('\n')}\n`);
+ }
+
console.log(name);
console.table(result);
+
resolve(result);
}
});
diff --git a/test/scripts/extend/tag.js b/test/scripts/extend/tag.js
index 193ee64890..a89f6f7c8b 100644
--- a/test/scripts/extend/tag.js
+++ b/test/scripts/extend/tag.js
@@ -1,7 +1,6 @@
'use strict';
const { spy } = require('sinon');
-const Promise = require('bluebird');
describe('Tag', () => {
const Tag = require('../../../lib/extend/tag');
@@ -19,7 +18,7 @@ describe('Tag', () => {
it('register() - async', async () => {
const tag = new Tag();
- tag.register('test', (args, content) => Promise.resolve(args.join(' ')), { async: true });
+ tag.register('test', async (args, content) => args.join(' '), { async: true });
const result = await tag.render('{% test foo bar %}');
result.should.eql('foo bar');
@@ -43,7 +42,7 @@ describe('Tag', () => {
it('register() - async block', async () => {
const tag = new Tag();
- tag.register('test', (args, content) => Promise.resolve(args.join(' ') + ' ' + content), { ends: true, async: true });
+ tag.register('test', async (args, content) => args.join(' ') + ' ' + content, { ends: true, async: true });
const str = [
'{% test foo bar %}',
@@ -81,9 +80,7 @@ describe('Tag', () => {
const tag = new Tag();
tag.register('test', (args, content) => content, {ends: true, async: true});
- tag.register('async', (args, content) => {
- return Promise.resolve(args.join(' ') + ' ' + content);
- }, { ends: true, async: true });
+ tag.register('async', async (args, content) => args.join(' ') + ' ' + content, { ends: true, async: true });
const str = [
'{% test %}',
@@ -136,7 +133,7 @@ describe('Tag', () => {
it('unregister()', () => {
const tag = new Tag();
- tag.register('test', (args, content) => Promise.resolve(args.join(' ')), {async: true});
+ tag.register('test', async (args, content) => args.join(' '), {async: true});
tag.unregister('test');
return tag.render('{% test foo bar %}')
diff --git a/test/scripts/extend/tag_errors.js b/test/scripts/extend/tag_errors.js
index 1b616f2cfc..6a674430b8 100644
--- a/test/scripts/extend/tag_errors.js
+++ b/test/scripts/extend/tag_errors.js
@@ -124,4 +124,26 @@ describe('Tag Errors', () => {
err.message.should.contains(source);
}
});
+
+ it('source file path 2', async () => {
+ const source = '_posts/hello-world.md';
+ const tag = new Tag();
+
+ tag.register('test',
+ (args, content) => {},
+ { ends: true });
+
+ const body = [
+ '{% test %}',
+ '${#var}',
+ '{% endtest %}'
+ ].join('\n');
+
+ try {
+ await tag.render(body, { source });
+ } catch (err) {
+ err.should.have.property('message');
+ err.message.should.contains(source);
+ }
+ });
});
diff --git a/test/scripts/helpers/is.js b/test/scripts/helpers/is.js
index 02a43808f9..fa8acd9551 100644
--- a/test/scripts/helpers/is.js
+++ b/test/scripts/helpers/is.js
@@ -25,6 +25,13 @@ describe('is', () => {
await is.home.call({page: {}}).should.be.false;
});
+ it('is_home_first_page', async () => {
+ await is.home_first_page.call({page: {__index: true, current: 1}}).should.be.true;
+ await is.home_first_page.call({page: {__index: true, current: 2}}).should.be.false;
+ await is.home_first_page.call({page: {__index: true}}).should.be.false;
+ await is.home_first_page.call({page: {}}).should.be.false;
+ });
+
it('is_post', async () => {
await is.post.call({page: {__post: true}}).should.be.true;
await is.post.call({page: {}}).should.be.false;
diff --git a/test/scripts/helpers/mail_to.js b/test/scripts/helpers/mail_to.js
index 4d961709bc..bcac9970c7 100644
--- a/test/scripts/helpers/mail_to.js
+++ b/test/scripts/helpers/mail_to.js
@@ -1,7 +1,5 @@
'use strict';
-const qs = require('querystring');
-
describe('mail_to', () => {
const Hexo = require('../../../lib/hexo');
const hexo = new Hexo(__dirname);
@@ -33,7 +31,7 @@ describe('mail_to', () => {
it('cc (string)', () => {
const data = {cc: 'abc@abc.com'};
- const querystring = qs.stringify(data);
+ const querystring = new URLSearchParams(data).toString();
mailto('abc@example.com', 'Email', {cc: 'abc@abc.com'})
.should.eql('Email');
@@ -41,7 +39,7 @@ describe('mail_to', () => {
it('cc (array)', () => {
const data = {cc: 'abc@abc.com,bcd@bcd.com'};
- const querystring = qs.stringify(data);
+ const querystring = new URLSearchParams(data).toString();
mailto('abc@example.com', 'Email', {cc: ['abc@abc.com', 'bcd@bcd.com']})
.should.eql('Email');
@@ -49,7 +47,7 @@ describe('mail_to', () => {
it('bcc (string)', () => {
const data = {bcc: 'abc@abc.com'};
- const querystring = qs.stringify(data);
+ const querystring = new URLSearchParams(data).toString();
mailto('abc@example.com', 'Email', {bcc: 'abc@abc.com'})
.should.eql('Email');
@@ -57,7 +55,7 @@ describe('mail_to', () => {
it('bcc (array)', () => {
const data = {bcc: 'abc@abc.com,bcd@bcd.com'};
- const querystring = qs.stringify(data);
+ const querystring = new URLSearchParams(data).toString();
mailto('abc@example.com', 'Email', {bcc: ['abc@abc.com', 'bcd@bcd.com']})
.should.eql('Email');
diff --git a/test/scripts/helpers/open_graph.js b/test/scripts/helpers/open_graph.js
index f8b78ffb61..c709e7fa17 100644
--- a/test/scripts/helpers/open_graph.js
+++ b/test/scripts/helpers/open_graph.js
@@ -301,11 +301,10 @@ describe('open_graph', () => {
});
it('images - resolve relative path when site is hosted in subdirectory', () => {
- const urlFn = require('url');
const config = hexo.config;
- config.url = urlFn.resolve(config.url, 'blog');
+ config.url = new URL('blog', config.url).toString();
config.root = 'blog';
- const postUrl = urlFn.resolve(config.url, '/foo/bar/index.html');
+ const postUrl = new URL('/foo/bar/index.html', config.url).toString();
const result = openGraph.call({
page: {},
@@ -314,7 +313,7 @@ describe('open_graph', () => {
url: postUrl
}, {images: 'test.jpg'});
- result.should.have.string(meta({property: 'og:image', content: urlFn.resolve(config.url, '/foo/bar/test.jpg')}));
+ result.should.have.string(meta({property: 'og:image', content: new URL('/foo/bar/test.jpg', config.url).toString()}));
});
it('twitter_image - default same as og:image', () => {
diff --git a/test/scripts/helpers/paginator.js b/test/scripts/helpers/paginator.js
index ea738d509c..f1aeb63390 100644
--- a/test/scripts/helpers/paginator.js
+++ b/test/scripts/helpers/paginator.js
@@ -302,4 +302,43 @@ describe('paginator', () => {
''
].join(''));
});
+
+ it('custom_class', () => {
+ const result = paginator({
+ current: 2,
+ current_class: 'current-class',
+ space_class: 'space-class',
+ page_class: 'page-class',
+ prev_class: 'prev-class',
+ next_class: 'next-class'
+ });
+
+ result.should.eql([
+ 'Prev',
+ '1',
+ '2',
+ '3',
+ '4',
+ '…',
+ '10',
+ 'Next'
+ ].join(''));
+ });
+
+ it('force_prev_next', () => {
+ const result = paginator({
+ current: 1,
+ force_prev_next: true
+ });
+
+ result.should.eql([
+ 'Prev',
+ '1',
+ '2',
+ '3',
+ '…',
+ '10',
+ 'Next'
+ ].join(''));
+ });
});
diff --git a/test/scripts/helpers/toc.js b/test/scripts/helpers/toc.js
index 08d7c3ebeb..47f0014790 100644
--- a/test/scripts/helpers/toc.js
+++ b/test/scripts/helpers/toc.js
@@ -476,4 +476,83 @@ describe('toc', () => {
toc(input, { list_number: true, class: className }).should.eql(expected);
});
+
+ it('custom class', () => {
+ const className = 'foo';
+ const childClassName = 'bar';
+ const expected = [
+ '
',
+ '- ',
+ '',
+ '1. ', // list_number enabled
+ 'Title 1',
+ '',
+ '
',
+ '- ',
+ '',
+ '1.1. ', // list_number enabled
+ 'Title 1.1',
+ '',
+ '
',
+ '- ',
+ '',
+ '1.1.1. ', // list_number enabled
+ 'Title 1.1.1',
+ '',
+ '
',
+ '
',
+ ' ',
+ '- ',
+ '',
+ '1.2. ', // list_number enabled
+ 'Title 1.2',
+ '',
+ '
',
+ '- ',
+ '',
+ '1.3. ', // list_number enabled
+ 'Title 1.3',
+ '',
+ '
',
+ '- ',
+ '',
+ '1.3.1. ', // list_number enabled
+ 'Title 1.3.1',
+ '',
+ '
',
+ '
',
+ ' ',
+ '
',
+ ' ',
+ '- ',
+ '',
+ '2. ', // list_number enabled
+ 'Title 2',
+ '',
+ '
',
+ '- ',
+ '',
+ '2.1. ', // list_number enabled
+ 'Title 2.1',
+ '',
+ '
',
+ '
',
+ ' ',
+ '- ',
+ '',
+ '3. ', // list_number enabled
+ 'Title should escape &, <, ', and "',
+ '',
+ '
',
+ '- ',
+ '',
+ '4. ', // list_number enabled
+ 'Chapter 1 should be printed to toc',
+ '',
+ '
',
+ '
'
+ ].join('');
+
+ toc(html, { class: 'foo', class_child: 'bar' }).should.eql(expected);
+ });
});
diff --git a/test/scripts/tags/post_link.js b/test/scripts/tags/post_link.js
index e0a9d3c6a5..473edd5f41 100644
--- a/test/scripts/tags/post_link.js
+++ b/test/scripts/tags/post_link.js
@@ -57,11 +57,11 @@ describe('post_link', () => {
.should.eql('This is a Bold "statement"');
});
- it('no slug', () => {
- postLink([]).should.eql('Post not found: Invalid post_link');
+ it('should throw if no slug', () => {
+ should.throw(() => postLink([]), Error, /Post not found: "undefined" doesn't exist for \{% post_link %\}/);
});
- it('post not found', () => {
- postLink(['bar']).should.eql('Post not found: bar');
+ it('should throw if post not found', () => {
+ should.throw(() => postLink(['bar']), Error, /Post not found: post_link bar\./);
});
});