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 += `
      1. `; + result += `
      2. `; 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([ + '', + '1', + '2', + '3', + '4', + '', + '10', + '' + ].join('')); + }); + + it('force_prev_next', () => { + const result = paginator({ + current: 1, + force_prev_next: true + }); + + result.should.eql([ + '', + '1', + '2', + '3', + '', + '10', + '' + ].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. ', + '', + '1. ', // list_number enabled + 'Title 1', + '', + '
            ', + '
          1. ', + '', + '1.1. ', // list_number enabled + 'Title 1.1', + '', + '
              ', + '
            1. ', + '', + '1.1.1. ', // list_number enabled + 'Title 1.1.1', + '', + '
            2. ', + '
            ', + '
          2. ', + '
          3. ', + '', + '1.2. ', // list_number enabled + 'Title 1.2', + '', + '
          4. ', + '
          5. ', + '', + '1.3. ', // list_number enabled + 'Title 1.3', + '', + '
              ', + '
            1. ', + '', + '1.3.1. ', // list_number enabled + 'Title 1.3.1', + '', + '
            2. ', + '
            ', + '
          6. ', + '
          ', + '
        2. ', + '
        3. ', + '', + '2. ', // list_number enabled + 'Title 2', + '', + '
            ', + '
          1. ', + '', + '2.1. ', // list_number enabled + 'Title 2.1', + '', + '
          2. ', + '
          ', + '
        4. ', + '
        5. ', + '', + '3. ', // list_number enabled + 'Title should escape &, <, ', and "', + '', + '
        6. ', + '
        7. ', + '', + '4. ', // list_number enabled + 'Chapter 1 should be printed to toc', + '', + '
        8. ', + '
        ' + ].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\./); }); });