diff --git a/.gitattributes b/.gitattributes
index 620b56b..34c7ad6 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1 +1,2 @@
source/lib/* linguist-vendored
+test/* linguist-vendored
diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml
index c3d3877..1b04287 100644
--- a/.github/workflows/linter.yml
+++ b/.github/workflows/linter.yml
@@ -10,4 +10,5 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v1
- run: npm install
+ - run: npm run eslint
- run: npm test
diff --git a/.npmignore b/.npmignore
index 0b9f150..ecb49b3 100644
--- a/.npmignore
+++ b/.npmignore
@@ -1,4 +1,5 @@
.github/
+test/
.editorconfig
.eslintrc.json
.gitattributes
diff --git a/gulpfile.js b/gulpfile.js
deleted file mode 100644
index 6a39a1b..0000000
--- a/gulpfile.js
+++ /dev/null
@@ -1,45 +0,0 @@
-const fs = require('fs');
-const path = require('path');
-const gulp = require('gulp');
-const shell = require('gulp-shell');
-const yaml = require('js-yaml');
-
-gulp.task('lint', shell.task([
- 'npm run eslint'
-]));
-
-gulp.task('lint:stylus', shell.task([
- 'npm run stylint'
-]));
-
-gulp.task('validate:config', cb => {
- const themeConfig = fs.readFileSync(path.join(__dirname, '_config.yml'));
-
- try {
- yaml.safeLoad(themeConfig);
- return cb();
- } catch (error) {
- return cb(new Error(error));
- }
-});
-
-gulp.task('validate:languages', cb => {
- const languagesPath = path.join(__dirname, 'languages');
- const errors = [];
-
- fs.readdirSync(languagesPath).forEach(lang => {
- if (!lang.endsWith('.yml')) return;
- const languagePath = path.join(languagesPath, lang);
- try {
- yaml.safeLoad(fs.readFileSync(languagePath), {
- filename: path.relative(__dirname, languagePath)
- });
- } catch (error) {
- errors.push(error);
- }
- });
-
- return errors.length === 0 ? cb() : cb(errors);
-});
-
-gulp.task('default', gulp.series('lint', 'validate:config', 'validate:languages'));
diff --git a/package.json b/package.json
index f4f6009..b42a0ca 100644
--- a/package.json
+++ b/package.json
@@ -2,11 +2,11 @@
"name": "hexo-theme-next",
"version": "8.0.0-rc.4",
"description": "Elegant and powerful theme for Hexo.",
- "main": "gulpfile.js",
+ "main": "package.json",
"scripts": {
- "test": "gulp",
- "eslint": "eslint source/js scripts/",
- "stylint": "stylint source/css/"
+ "eslint": "eslint scripts/ source/js test/",
+ "stylint": "stylint source/css/",
+ "test": "mocha test/index.js"
},
"repository": {
"type": "git",
@@ -24,17 +24,19 @@
},
"homepage": "https://theme-next.js.org",
"devDependencies": {
+ "chai": "4.2.0",
"eslint": "7.3.1",
"eslint-config-theme-next": "1.2.0",
- "gulp": "4.0.2",
- "gulp-shell": "0.8.0",
+ "hexo": "5.0.0",
+ "hexo-renderer-marked": "3.0.0",
"husky": "4.2.5",
"js-yaml": "3.14.0",
+ "mocha": "8.0.1",
"stylint": "2.0.0"
},
"husky": {
"hooks": {
- "pre-commit": "gulp"
+ "pre-commit": "npm run eslint && npm test"
}
}
}
diff --git a/scripts/helpers/engine.js b/scripts/helpers/engine.js
index fdcb18d..2144ab2 100644
--- a/scripts/helpers/engine.js
+++ b/scripts/helpers/engine.js
@@ -3,6 +3,11 @@
'use strict';
const crypto = require('crypto');
+const nextFont = require('./font');
+const nextUrl = require('./next-url');
+
+hexo.extend.helper.register('next_font', nextFont);
+hexo.extend.helper.register('next_url', nextUrl);
hexo.extend.helper.register('next_inject', function(point) {
return this.theme.injects[point]
diff --git a/scripts/helpers/font.js b/scripts/helpers/font.js
index 27527f7..57b95bf 100644
--- a/scripts/helpers/font.js
+++ b/scripts/helpers/font.js
@@ -1,9 +1,7 @@
-/* global hexo */
-
'use strict';
// https://developers.google.com/fonts/docs/getting_started
-hexo.extend.helper.register('next_font', function() {
+module.exports = function() {
const config = this.theme.font;
if (!config || !config.enable) return '';
@@ -24,4 +22,4 @@ hexo.extend.helper.register('next_font', function() {
// Merge extra parameters to the final processed font string
return fontFamilies ? `` : '';
-});
+};
diff --git a/scripts/helpers/next-url.js b/scripts/helpers/next-url.js
index 7212b44..240459a 100644
--- a/scripts/helpers/next-url.js
+++ b/scripts/helpers/next-url.js
@@ -1,16 +1,13 @@
-/* global hexo */
-
'use strict';
const { htmlTag } = require('hexo-util');
const { parse } = require('url');
-hexo.extend.helper.register('next_url', function(path, text, options = {}) {
- const { config } = this;
+module.exports = function(path, text, options = {}) {
+ const { config, theme } = this;
const data = parse(path);
const siteHost = parse(config.url).hostname || config.url;
- const theme = hexo.theme.config;
let exturl = '';
let tag = 'a';
let attrs = { href: this.url_for(path) };
@@ -58,4 +55,4 @@ hexo.extend.helper.register('next_url', function(path, text, options = {}) {
}
return htmlTag(tag, attrs, decodeURI(text), false);
-});
+};
diff --git a/scripts/tags/button.js b/scripts/tags/button.js
index 21c5b4d..8379ffd 100644
--- a/scripts/tags/button.js
+++ b/scripts/tags/button.js
@@ -2,11 +2,9 @@
* button.js | https://theme-next.js.org/docs/tag-plugins/button
*/
-/* global hexo */
-
'use strict';
-function postButton(args) {
+module.exports = ctx => function(args) {
args = args.join(' ').split(',');
const url = args[0];
const text = (args[1] || '').trim();
@@ -14,7 +12,7 @@ function postButton(args) {
const title = (args[3] || '').trim();
if (!url) {
- hexo.log.warn('URL can NOT be empty.');
+ ctx.log.warn('URL can NOT be empty.');
}
if (icon.length > 0) {
if (!icon.startsWith('fa')) icon = 'fa fa-' + icon;
@@ -22,7 +20,4 @@ function postButton(args) {
}
return ` 0 ? ` title="${title}"` : ''}>${icon}${text}`;
-}
-
-hexo.extend.tag.register('button', postButton, {ends: false});
-hexo.extend.tag.register('btn', postButton, {ends: false});
+};
diff --git a/scripts/tags/caniuse.js b/scripts/tags/caniuse.js
index 18cfe70..ec0e549 100644
--- a/scripts/tags/caniuse.js
+++ b/scripts/tags/caniuse.js
@@ -2,22 +2,17 @@
* caniuse.js | https://theme-next.js.org/docs/tag-plugins/caniuse
*/
-/* global hexo */
-
'use strict';
-function caniUse(args) {
+module.exports = ctx => function(args) {
args = args.join('').split('@');
const feature = args[0];
const periods = args[1] || 'current';
if (!feature) {
- hexo.log.warn('Caniuse feature can NOT be empty.');
+ ctx.log.warn('Caniuse feature can NOT be empty.');
return '';
}
return ``;
-}
-
-hexo.extend.tag.register('caniuse', caniUse);
-hexo.extend.tag.register('can', caniUse);
+};
diff --git a/scripts/tags/center-quote.js b/scripts/tags/center-quote.js
index 425d531..169be33 100644
--- a/scripts/tags/center-quote.js
+++ b/scripts/tags/center-quote.js
@@ -2,15 +2,10 @@
* center-quote.js | https://theme-next.js.org/docs/tag-plugins/
*/
-/* global hexo */
-
'use strict';
-function centerQuote(args, content) {
+module.exports = ctx => function(args, content) {
return `
-${hexo.render.renderSync({ text: content, engine: 'markdown' })}
+${ctx.render.renderSync({ text: content, engine: 'markdown' })}
`;
-}
-
-hexo.extend.tag.register('centerquote', centerQuote, {ends: true});
-hexo.extend.tag.register('cq', centerQuote, {ends: true});
+};
diff --git a/scripts/tags/group-pictures.js b/scripts/tags/group-pictures.js
index 0ab46e8..4c20c73 100644
--- a/scripts/tags/group-pictures.js
+++ b/scripts/tags/group-pictures.js
@@ -2,8 +2,6 @@
* group-pictures.js | https://theme-next.js.org/docs/tag-plugins/group-pictures
*/
-/* global hexo */
-
'use strict';
const LAYOUTS = {
@@ -123,17 +121,14 @@ const templates = {
}
};
-function groupPicture(args, content) {
+module.exports = ctx => function(args, content) {
args = args[0].split('-');
const group = parseInt(args[0], 10);
const layout = parseInt(args[1], 10);
- content = hexo.render.renderSync({text: content, engine: 'markdown'});
+ content = ctx.render.renderSync({ text: content, engine: 'markdown' });
const pictures = content.match(/
/g);
return `${templates.dispatch(pictures, group, layout)}
`;
-}
-
-hexo.extend.tag.register('grouppicture', groupPicture, {ends: true});
-hexo.extend.tag.register('gp', groupPicture, {ends: true});
+};
diff --git a/scripts/tags/index.js b/scripts/tags/index.js
new file mode 100644
index 0000000..fe06950
--- /dev/null
+++ b/scripts/tags/index.js
@@ -0,0 +1,55 @@
+/* global hexo */
+
+'use strict';
+
+const postButton = require('./button')(hexo);
+
+hexo.extend.tag.register('button', postButton);
+hexo.extend.tag.register('btn', postButton);
+
+const caniUse = require('./caniuse')(hexo);
+
+hexo.extend.tag.register('caniuse', caniUse);
+hexo.extend.tag.register('can', caniUse);
+
+const centerQuote = require('./center-quote')(hexo);
+
+hexo.extend.tag.register('centerquote', centerQuote, true);
+hexo.extend.tag.register('cq', centerQuote, true);
+
+const groupPicture = require('./group-pictures')(hexo);
+
+hexo.extend.tag.register('grouppicture', groupPicture, true);
+hexo.extend.tag.register('gp', groupPicture, true);
+
+const postLabel = require('./label')(hexo);
+
+hexo.extend.tag.register('label', postLabel);
+
+const linkGrid = require('./link-grid');
+
+hexo.extend.tag.register('linkgrid', linkGrid, true);
+hexo.extend.tag.register('lg', linkGrid, true);
+
+const mermaid = require('./mermaid');
+
+hexo.extend.tag.register('mermaid', mermaid, true);
+
+const postNote = require('./note')(hexo);
+
+hexo.extend.tag.register('note', postNote, true);
+hexo.extend.tag.register('subnote', postNote, true);
+
+const pdf = require('./pdf')(hexo);
+
+hexo.extend.tag.register('pdf', pdf);
+
+const postTabs = require('./tabs')(hexo);
+
+hexo.extend.tag.register('tabs', postTabs, true);
+hexo.extend.tag.register('subtabs', postTabs, true);
+hexo.extend.tag.register('subsubtabs', postTabs, true);
+
+const postVideo = require('./video');
+
+hexo.extend.tag.register('video', postVideo);
diff --git a/scripts/tags/label.js b/scripts/tags/label.js
index efab944..80f3c27 100644
--- a/scripts/tags/label.js
+++ b/scripts/tags/label.js
@@ -2,18 +2,14 @@
* label.js | https://theme-next.js.org/docs/tag-plugins/label
*/
-/* global hexo */
-
'use strict';
-function postLabel(args) {
+module.exports = ctx => function(args) {
args = args.join(' ').split('@');
const classes = args[0] || 'default';
const text = args[1] || '';
- if (!text) hexo.log.warn('Label text must be defined!');
+ if (!text) ctx.log.warn('Label text must be defined!');
return `${text}`;
-}
-
-hexo.extend.tag.register('label', postLabel, {ends: false});
+};
diff --git a/scripts/tags/link-grid.js b/scripts/tags/link-grid.js
index da83cf8..c95421e 100644
--- a/scripts/tags/link-grid.js
+++ b/scripts/tags/link-grid.js
@@ -2,17 +2,15 @@
* link-grid.js | https://theme-next.js.org/docs/tag-plugins/link-grid
*/
-/* global hexo */
-
'use strict';
-function linkGrid(args, content) {
+module.exports = function(args, content) {
const image = args[0] || '/images/avatar.gif';
const delimiter = args[1] || '|';
const comment = args[2] || '%';
- const links = content.split('\n').map(item => {
- item = item.split(delimiter).map(arg => arg.trim());
+ const links = content.split('\n').filter(line => line.trim() !== '').map(line => {
+ const item = line.split(delimiter).map(arg => arg.trim());
if (item[0][0] === comment) return '';
return `
@@ -21,7 +19,4 @@ function linkGrid(args, content) {
`;
});
return `${links.join('')}
`;
-}
-
-hexo.extend.tag.register('linkgrid', linkGrid, {ends: true});
-hexo.extend.tag.register('lg', linkGrid, {ends: true});
+};
diff --git a/scripts/tags/mermaid.js b/scripts/tags/mermaid.js
index 2071568..e2a029c 100644
--- a/scripts/tags/mermaid.js
+++ b/scripts/tags/mermaid.js
@@ -2,15 +2,11 @@
* mermaid.js | https://theme-next.js.org/docs/tag-plugins/mermaid
*/
-/* global hexo */
-
'use strict';
-function mermaid(args, content) {
+module.exports = function(args, content) {
return `
${args.join(' ')}
${content}
`;
-}
-
-hexo.extend.tag.register('mermaid', mermaid, {ends: true});
+};
diff --git a/scripts/tags/note.js b/scripts/tags/note.js
index cfc0857..1c81c4a 100644
--- a/scripts/tags/note.js
+++ b/scripts/tags/note.js
@@ -2,11 +2,9 @@
* note.js | https://theme-next.js.org/docs/tag-plugins/note
*/
-/* global hexo */
-
'use strict';
-function postNote(args, content) {
+module.exports = ctx => function(args, content) {
const keywords = ['default', 'primary', 'info', 'success', 'warning', 'danger', 'no-icon'];
const className = [];
const summary = [];
@@ -17,14 +15,11 @@ function postNote(args, content) {
className.push(arg);
}
});
- content = hexo.render.renderSync({ text: content, engine: 'markdown' });
+ content = ctx.render.renderSync({ text: content, engine: 'markdown' });
if (summary.length === 0) {
return `${content}
`;
}
- return `${hexo.render.renderSync({ text: summary.join(' '), engine: 'markdown' })}
+ return `${ctx.render.renderSync({ text: summary.join(' '), engine: 'markdown' })}
${content}
`;
-}
-
-hexo.extend.tag.register('note', postNote, {ends: true});
-hexo.extend.tag.register('subnote', postNote, {ends: true});
+};
diff --git a/scripts/tags/pdf.js b/scripts/tags/pdf.js
index 78303ca..8145365 100644
--- a/scripts/tags/pdf.js
+++ b/scripts/tags/pdf.js
@@ -2,13 +2,9 @@
* pdf.js | https://theme-next.js.org/docs/tag-plugins/pdf
*/
-/* global hexo */
-
'use strict';
-function pdf(args) {
- const theme = hexo.theme.config;
+module.exports = ctx => function(args) {
+ const theme = ctx.theme.config;
return ``;
-}
-
-hexo.extend.tag.register('pdf', pdf, {ends: false});
+};
diff --git a/scripts/tags/tabs.js b/scripts/tags/tabs.js
index e2e3432..8611f6e 100644
--- a/scripts/tags/tabs.js
+++ b/scripts/tags/tabs.js
@@ -2,11 +2,9 @@
* tabs.js | https://theme-next.js.org/docs/tag-plugins/tabs
*/
-/* global hexo */
-
'use strict';
-function postTabs(args, content) {
+module.exports = ctx => function(args, content) {
const tabBlock = /\n([\w\W\s\S]*?)/g;
args = args.join(' ').split(',');
@@ -19,7 +17,7 @@ function postTabs(args, content) {
let tabNav = '';
let tabContent = '';
- if (!tabName) hexo.log.warn('Tabs block must have unique name!');
+ if (!tabName) ctx.log.warn('Tabs block must have unique name!');
while ((match = tabBlock.exec(content)) !== null) {
matches.push(match[1]);
@@ -30,7 +28,7 @@ function postTabs(args, content) {
let [caption = '', icon = ''] = matches[i].split('@');
let postContent = matches[i + 1];
- postContent = hexo.render.renderSync({text: postContent, engine: 'markdown'}).trim();
+ postContent = ctx.render.renderSync({ text: postContent, engine: 'markdown' }).trim();
const abbr = tabName + ' ' + ++tabId;
const href = abbr.toLowerCase().split(' ').join('-');
@@ -52,8 +50,4 @@ function postTabs(args, content) {
tabContent = `${tabContent}
`;
return `${tabNav + tabContent}
`;
-}
-
-hexo.extend.tag.register('tabs', postTabs, {ends: true});
-hexo.extend.tag.register('subtabs', postTabs, {ends: true});
-hexo.extend.tag.register('subsubtabs', postTabs, {ends: true});
+};
diff --git a/scripts/tags/video.js b/scripts/tags/video.js
index 3452ebf..9f4e70c 100644
--- a/scripts/tags/video.js
+++ b/scripts/tags/video.js
@@ -2,12 +2,8 @@
* video.js | https://theme-next.js.org/docs/tag-plugins/
*/
-/* global hexo */
-
'use strict';
-function postVideo(args) {
+module.exports = function(args) {
return ``;
-}
-
-hexo.extend.tag.register('video', postVideo, {ends: false});
+};
diff --git a/test/helpers/font.js b/test/helpers/font.js
new file mode 100644
index 0000000..e1eeb97
--- /dev/null
+++ b/test/helpers/font.js
@@ -0,0 +1,86 @@
+'use strict';
+
+require('chai').should();
+const Hexo = require('hexo');
+const hexo = new Hexo();
+
+const fontStyles = ':300,300italic,400,400italic,700,700italic';
+const fontHost = '//fonts.googleapis.com';
+
+describe('font', () => {
+ const nextFont = require('../../scripts/helpers/font').bind(hexo);
+
+ before(() => {
+ hexo.theme.font = {};
+ });
+
+ it('font disabled', () => {
+ hexo.theme.font.enable = false;
+ hexo.theme.font.title = {
+ family : 'Amatic SC',
+ external: true
+ };
+ nextFont().should.eql('');
+ });
+
+ it('no external font', () => {
+ hexo.theme.font.enable = true;
+ hexo.theme.font.title = {
+ family : 'Amatic SC',
+ external: false
+ };
+ nextFont().should.eql('');
+ });
+
+ it('trivial', () => {
+ hexo.theme.font.enable = true;
+ hexo.theme.font.title = {
+ family : 'Amatic SC',
+ external: true
+ };
+ hexo.theme.font.headings = {
+ family : 'Palatino',
+ external: false
+ };
+ nextFont().should.eql(``);
+ });
+
+ it('multiple', () => {
+ hexo.theme.font.enable = true;
+ hexo.theme.font.title = {
+ family : 'Amatic SC',
+ external: true
+ };
+ hexo.theme.font.headings = {
+ family : 'Palatino',
+ external: true
+ };
+ nextFont().should.eql(``);
+ });
+
+ it('duplicate', () => {
+ hexo.theme.font.enable = true;
+ hexo.theme.font.title = {
+ family : 'Palatino',
+ external: true
+ };
+ hexo.theme.font.headings = {
+ family : 'Palatino',
+ external: true
+ };
+ nextFont().should.eql(``);
+ });
+
+ it('fallback font', () => {
+ hexo.theme.font.enable = true;
+ hexo.theme.font.title = {
+ family : 'Roboto Slab, Noto Serif SC',
+ external: true
+ };
+ hexo.theme.font.headings = {
+ family : 'Palatino',
+ external: true
+ };
+ nextFont().should.eql(``);
+ });
+});
diff --git a/test/helpers/index.js b/test/helpers/index.js
new file mode 100644
index 0000000..3ef5dde
--- /dev/null
+++ b/test/helpers/index.js
@@ -0,0 +1,6 @@
+'use strict';
+
+describe('Helpers', () => {
+ require('./font');
+ require('./next-url');
+});
diff --git a/test/helpers/next-url.js b/test/helpers/next-url.js
new file mode 100644
index 0000000..a9fef70
--- /dev/null
+++ b/test/helpers/next-url.js
@@ -0,0 +1,40 @@
+'use strict';
+
+require('chai').should();
+const Hexo = require('hexo');
+const hexo = new Hexo();
+
+function btoa(str) {
+ return Buffer.from(str).toString('base64');
+}
+
+describe('next-url', () => {
+ const nextUrl = require('../../scripts/helpers/next-url').bind(hexo);
+
+ before(() => {
+ hexo.config.url = 'https://example.com';
+ hexo.url_for = require('hexo/lib/plugins/helper/url_for').bind(hexo);
+ });
+
+ it('text', () => {
+ nextUrl('/child/', 'Text').should.eql('Text');
+ });
+
+ it('icon', () => {
+ nextUrl('/child/', '').should.eql('');
+ });
+
+ it('class', () => {
+ nextUrl('/child/', 'Text', { class: 'theme-link' }).should.eql('Text');
+ });
+
+ it('external', () => {
+ nextUrl('https://theme-next.js.org', 'Text').should.eql('Text');
+ });
+
+ it('exturl enabled', () => {
+ hexo.theme.exturl = true;
+ const encoded = btoa('https://theme-next.js.org');
+ nextUrl('https://theme-next.js.org', 'Text').should.eql(`Text`);
+ });
+});
diff --git a/test/index.js b/test/index.js
new file mode 100644
index 0000000..e6088b6
--- /dev/null
+++ b/test/index.js
@@ -0,0 +1,7 @@
+'use strict';
+
+describe('NexT', () => {
+ require('./helpers');
+ require('./tags');
+ require('./validate');
+});
diff --git a/test/tags/button.js b/test/tags/button.js
new file mode 100644
index 0000000..f74583a
--- /dev/null
+++ b/test/tags/button.js
@@ -0,0 +1,33 @@
+'use strict';
+
+require('chai').should();
+const Hexo = require('hexo');
+const hexo = new Hexo();
+
+describe('button', () => {
+ const postButton = require('../../scripts/tags/button')(hexo);
+
+ it('only url', () => {
+ postButton(['#']).should.eql('');
+ });
+
+ it('url and text', () => {
+ postButton('#, Hello world'.split(' ')).should.eql('Hello world');
+ });
+
+ it('url and icon (Font Awesome 4)', () => {
+ postButton('#,, home fa-5x'.split(' ')).should.eql('');
+ });
+
+ it('url and icon', () => {
+ postButton('#,, fab fa-fort-awesome fa-5x'.split(' ')).should.eql('');
+ });
+
+ it('url, text and title', () => {
+ postButton('#, Hello world,, Title'.split(' ')).should.eql('Hello world');
+ });
+
+ it('url, text, icon and title', () => {
+ postButton('#, Hello world, home, Title'.split(' ')).should.eql('Hello world');
+ });
+});
diff --git a/test/tags/caniuse.js b/test/tags/caniuse.js
new file mode 100644
index 0000000..1fe0e20
--- /dev/null
+++ b/test/tags/caniuse.js
@@ -0,0 +1,17 @@
+'use strict';
+
+require('chai').should();
+const Hexo = require('hexo');
+const hexo = new Hexo();
+
+describe('caniuse', () => {
+ const caniUse = require('../../scripts/tags/caniuse')(hexo);
+
+ it('only feature', () => {
+ caniUse(['loading-lazy-attr']).should.eql('');
+ });
+
+ it('feature and periods', () => {
+ caniUse('fetch @ future_3,future_2,future_1'.split(' ')).should.eql('');
+ });
+});
diff --git a/test/tags/center-quote.js b/test/tags/center-quote.js
new file mode 100644
index 0000000..0f20488
--- /dev/null
+++ b/test/tags/center-quote.js
@@ -0,0 +1,21 @@
+'use strict';
+
+require('chai').should();
+const Hexo = require('hexo');
+const hexo = new Hexo();
+
+const content = 'Test **Bold** *Italic*';
+const result = 'Test Bold Italic
';
+
+describe('center-quote', () => {
+ const centerQuote = require('../../scripts/tags/center-quote')(hexo);
+
+ before(() => hexo.init().then(() => hexo.loadPlugin(require.resolve('hexo-renderer-marked'))));
+
+ it('markdown content', () => {
+ centerQuote([], content).should.eql(`
+${result}
+
+
`);
+ });
+});
diff --git a/test/tags/group-pictures.js b/test/tags/group-pictures.js
new file mode 100644
index 0000000..c2b6695
--- /dev/null
+++ b/test/tags/group-pictures.js
@@ -0,0 +1,45 @@
+'use strict';
+
+require('chai').should();
+const Hexo = require('hexo');
+const hexo = new Hexo();
+
+describe('group-pictures', () => {
+ const groupPicture = require('../../scripts/tags/group-pictures')(hexo);
+
+ before(() => hexo.init().then(() => hexo.loadPlugin(require.resolve('hexo-renderer-marked'))));
+
+ it('layout 3-1', () => {
+ groupPicture(['3-1'], `
+
+
+`).should.eql('');
+ });
+
+ it('layout 5-2', () => {
+ groupPicture(['5-2'], `
+
+
+
+
+`).should.eql('');
+ });
+
+ it('remove text', () => {
+ groupPicture(['3-1'], `
+
+Text
+
+Text
+`).should.eql('');
+ });
+
+ it('no layout', () => {
+ groupPicture(['NaN-NaN'], `
+
+
+
+
+`).should.eql('');
+ });
+});
diff --git a/test/tags/index.js b/test/tags/index.js
new file mode 100644
index 0000000..77806a4
--- /dev/null
+++ b/test/tags/index.js
@@ -0,0 +1,15 @@
+'use strict';
+
+describe('Tags', () => {
+ require('./button');
+ require('./caniuse');
+ require('./center-quote');
+ require('./group-pictures');
+ require('./label');
+ require('./link-grid');
+ require('./mermaid');
+ require('./note');
+ require('./pdf');
+ require('./tabs');
+ require('./video');
+});
diff --git a/test/tags/label.js b/test/tags/label.js
new file mode 100644
index 0000000..bf3c011
--- /dev/null
+++ b/test/tags/label.js
@@ -0,0 +1,17 @@
+'use strict';
+
+require('chai').should();
+const Hexo = require('hexo');
+const hexo = new Hexo();
+
+describe('label', () => {
+ const postLabel = require('../../scripts/tags/label')(hexo);
+
+ it('only text', () => {
+ postLabel('@Hello world'.split(' ')).should.eql('Hello world');
+ });
+
+ it('classes and text', () => {
+ postLabel('primary@Hello world'.split(' ')).should.eql('Hello world');
+ });
+});
diff --git a/test/tags/link-grid.js b/test/tags/link-grid.js
new file mode 100644
index 0000000..4dacb52
--- /dev/null
+++ b/test/tags/link-grid.js
@@ -0,0 +1,49 @@
+'use strict';
+
+require('chai').should();
+
+const result = `
+
+
Theme NexT
Stay Simple. Stay NexT.
+
+
+
+
Theme NexT
Stay Simple. Stay NexT.
+
+
`;
+
+describe('link-grid', () => {
+ const linkGrid = require('../../scripts/tags/link-grid');
+
+ it('default', () => {
+ linkGrid([], `
+Theme NexT | https://theme-next.js.org/ | Stay Simple. Stay NexT. | /images/sample.png
+Theme NexT | https://theme-next.js.org/ | Stay Simple. Stay NexT. | /images/sample.png`).should.eql(result);
+ });
+
+ it('comment', () => {
+ linkGrid([], `
+Theme NexT | https://theme-next.js.org/ | Stay Simple. Stay NexT. | /images/sample.png
+Theme NexT | https://theme-next.js.org/ | Stay Simple. Stay NexT. | /images/sample.png
+% Theme NexT | https://theme-next.js.org/ | Stay Simple. Stay NexT. | /images/sample.png`).should.eql(result);
+ });
+
+ it('default image', () => {
+ linkGrid(['/images/sample.png'], `
+Theme NexT | https://theme-next.js.org/ | Stay Simple. Stay NexT. |
+Theme NexT | https://theme-next.js.org/ | Stay Simple. Stay NexT. |`).should.eql(result);
+ });
+
+ it('custom delimiter', () => {
+ linkGrid(['/images/sample.png', ','], `
+Theme NexT , https://theme-next.js.org/ , Stay Simple. Stay NexT. , /images/sample.png
+Theme NexT , https://theme-next.js.org/ , Stay Simple. Stay NexT. , /images/sample.png`).should.eql(result);
+ });
+
+ it('custom delimiter and comment', () => {
+ linkGrid(['/images/sample.png', ',', '#'], `
+Theme NexT , https://theme-next.js.org/ , Stay Simple. Stay NexT. , /images/sample.png
+Theme NexT , https://theme-next.js.org/ , Stay Simple. Stay NexT. , /images/sample.png
+# Theme NexT , https://theme-next.js.org/ , Stay Simple. Stay NexT. , /images/sample.png`).should.eql(result);
+ });
+});
diff --git a/test/tags/mermaid.js b/test/tags/mermaid.js
new file mode 100644
index 0000000..546e75a
--- /dev/null
+++ b/test/tags/mermaid.js
@@ -0,0 +1,19 @@
+'use strict';
+
+require('chai').should();
+
+const result = `A[Hard] -->|Text| B(Round)
+B --> C{Decision}
+C -->|One| D[Result 1]
+C -->|Two| E[Result 2]`;
+
+describe('mermaid', () => {
+ const mermaid = require('../../scripts/tags/mermaid');
+
+ it('default', () => {
+ mermaid(['graph', 'TD'], result).should.eql(`
+graph TD
+${result}
+`);
+ });
+});
diff --git a/test/tags/note.js b/test/tags/note.js
new file mode 100644
index 0000000..3fff75c
--- /dev/null
+++ b/test/tags/note.js
@@ -0,0 +1,55 @@
+'use strict';
+
+require('chai').should();
+const Hexo = require('hexo');
+const hexo = new Hexo();
+
+const content = 'Test **Bold** *Italic*';
+const result = 'Test Bold Italic
';
+const args = 'This is a *summary*'.split(' ');
+const summary = 'This is a summary';
+
+describe('note', () => {
+ const postNote = require('../../scripts/tags/note')(hexo);
+
+ before(() => hexo.init().then(() => hexo.loadPlugin(require.resolve('hexo-renderer-marked'))));
+
+ it('only text', () => {
+ postNote([], content).should.eql(`
${result}
+
`);
+ });
+
+ it('classes and text', () => {
+ postNote(['primary'], content).should.eql(`${result}
+
`);
+ });
+
+ it('classes, no-icon and text', () => {
+ postNote(['primary', 'no-icon'], content).should.eql(`${result}
+
`);
+ });
+
+ it('summary and text', () => {
+ postNote(args, content).should.eql(`${summary}
+
+${result}
+
+ `);
+ });
+
+ it('classes, summary and text', () => {
+ postNote(['primary'].concat(args), content).should.eql(`${summary}
+
+${result}
+
+ `);
+ });
+
+ it('classes, no-icon, summary and text', () => {
+ postNote(['primary', 'no-icon'].concat(args), content).should.eql(`${summary}
+
+${result}
+
+ `);
+ });
+});
diff --git a/test/tags/pdf.js b/test/tags/pdf.js
new file mode 100644
index 0000000..6186677
--- /dev/null
+++ b/test/tags/pdf.js
@@ -0,0 +1,23 @@
+'use strict';
+
+require('chai').should();
+const Hexo = require('hexo');
+const hexo = new Hexo();
+
+describe('pdf', () => {
+ const pdf = require('../../scripts/tags/pdf')(hexo);
+
+ before(() => {
+ hexo.theme.config.pdf = {
+ height: '500px'
+ };
+ });
+
+ it('default', () => {
+ pdf(['https://example.com/sample.pdf']).should.eql('');
+ });
+
+ it('custom height', () => {
+ pdf(['https://example.com/sample.pdf', '1000px']).should.eql('');
+ });
+});
diff --git a/test/tags/tabs.js b/test/tags/tabs.js
new file mode 100644
index 0000000..db09ab1
--- /dev/null
+++ b/test/tags/tabs.js
@@ -0,0 +1,96 @@
+'use strict';
+
+require('chai').should();
+const Hexo = require('hexo');
+const hexo = new Hexo();
+
+const content = 'Test **Bold** *Italic*';
+const result = 'Test Bold Italic
';
+const container = '';
+
+describe('tabs', () => {
+ const postTabs = require('../../scripts/tags/tabs')(hexo);
+
+ before(() => hexo.init().then(() => hexo.loadPlugin(require.resolve('hexo-renderer-marked'))));
+
+ it('empty', () => {
+ postTabs(['name']).should.eql(`${container}
`);
+ });
+
+ it('default', () => {
+ postTabs(['name'],
+ `
+${content}
+
+
+
+${content}
+`).should.eql(`${container}name 1name 2`);
+ });
+
+ it('selected index', () => {
+ postTabs('name, 2'.split(' '),
+ `
+${content}
+
+
+
+${content}
+`).should.eql(`${container}name 1name 2`);
+ });
+
+ it('no tab selected', () => {
+ postTabs('name, -1'.split(' '),
+ `
+${content}
+
+
+
+${content}
+`).should.eql(`${container}name 1name 2`);
+ });
+
+ it('label', () => {
+ postTabs('name'.split(' '),
+ `
+${content}
+
+
+
+${content}
+`).should.eql(`${container}Tab 1Tab 2`);
+ });
+
+ it('icon (Font Awesome 4)', () => {
+ postTabs('name'.split(' '),
+ `
+${content}
+
+
+
+${content}
+`).should.eql(`${container}`);
+ });
+
+ it('icon', () => {
+ postTabs('name'.split(' '),
+ `
+${content}
+
+
+
+${content}
+`).should.eql(`${container}`);
+ });
+
+ it('label and icon', () => {
+ postTabs('name, -1'.split(' '),
+ `
+${content}
+
+
+
+${content}
+`).should.eql(`${container}Tab 1Tab 1`);
+ });
+});
diff --git a/test/tags/video.js b/test/tags/video.js
new file mode 100644
index 0000000..4e56cdb
--- /dev/null
+++ b/test/tags/video.js
@@ -0,0 +1,11 @@
+'use strict';
+
+require('chai').should();
+
+describe('video', () => {
+ const postVideo = require('../../scripts/tags/video');
+
+ it('default', () => {
+ postVideo(['https://example.com/sample.mp4']).should.eql('');
+ });
+});
diff --git a/test/validate/index.js b/test/validate/index.js
new file mode 100644
index 0000000..ccc67f2
--- /dev/null
+++ b/test/validate/index.js
@@ -0,0 +1,28 @@
+'use strict';
+
+const fs = require('fs');
+const path = require('path');
+const yaml = require('js-yaml');
+const should = require('chai').should();
+
+describe('Validate', () => {
+ it('config', () => {
+ const themeConfig = fs.readFileSync(path.join(__dirname, '../../_config.yml'));
+ should.not.throw(() => {
+ yaml.safeLoad(themeConfig);
+ });
+ });
+
+ it('language', () => {
+ const languagesPath = path.join(__dirname, '../../languages');
+ should.not.throw(() => {
+ fs.readdirSync(languagesPath).forEach(lang => {
+ if (!lang.endsWith('.yml')) return;
+ const languagePath = path.join(languagesPath, lang);
+ yaml.safeLoad(fs.readFileSync(languagePath), {
+ filename: path.relative(__dirname, languagePath)
+ });
+ });
+ });
+ });
+});