chore: Update lint configuration (ESLint 9, Prettier 3) (#2438)

* Update linting configuration (eslint, prettier)

* Fix lint issues following eslint prettier update

* Change ESLint config to allow boolean coercion

* Switch to default import name per docs

* Fix suppression of error details

* Update JSDoc comments

* Update waiForFunctin to provide error details

---------

Co-authored-by: Koy Zhuang <koy@ko8e24.top>
This commit is contained in:
John Hildenbiddle 2024-05-28 15:27:29 -05:00 committed by GitHub
parent 6552853fef
commit f5412dc7b0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
59 changed files with 745 additions and 995 deletions

View File

@ -1,11 +0,0 @@
# Directories
.git
build
dist
docs
lib
node_modules
# Files
**/*.md
server.js

View File

@ -1,83 +0,0 @@
const prettierConfig = require('./.prettierrc.json');
module.exports = {
root: true,
extends: [
'eslint:recommended',
'plugin:import/recommended',
'plugin:prettier/recommended', // Must be last
],
parser: '@babel/eslint-parser',
parserOptions: {
sourceType: 'module',
ecmaVersion: 2019,
},
plugins: ['prettier', 'import'],
env: {
browser: true,
es6: true,
node: true,
},
settings: {
'import/ignore': ['node_modules', '.json$'],
},
rules: {
camelcase: ['warn'],
curly: ['error', 'all'],
'dot-notation': ['error'],
eqeqeq: ['error'],
'handle-callback-err': ['error'],
'new-cap': ['error'],
'no-alert': ['error'],
'no-caller': ['error'],
'no-eval': ['error'],
'no-labels': ['error'],
'no-lonely-if': ['error'],
'no-new': ['error'],
'no-proto': ['error'],
'no-return-assign': ['error'],
'no-self-compare': ['error'],
'no-shadow-restricted-names': ['error'],
'no-shadow': [
'error',
{
allow: [
'Events',
'Fetch',
'Lifecycle',
'Render',
'Router',
'VirtualRoutes',
],
},
],
'no-unused-vars': ['error', { args: 'none' }],
'no-useless-call': ['error'],
'no-useless-escape': ['warn'],
'no-var': ['error'],
'no-void': ['error'],
'no-with': ['error'],
radix: ['error'],
'spaced-comment': ['error', 'always'],
strict: ['error', 'global'],
yoda: ['error', 'never'],
// Import rules
'import/imports-first': ['error'],
'import/newline-after-import': ['error'],
'import/no-duplicates': ['error'],
'import/no-mutable-exports': ['error'],
'import/no-named-as-default-member': ['error'],
'import/no-named-as-default': ['error'],
'import/no-unresolved': 'off',
'import/order': ['warn'],
// Prettier (Must be last)
'prettier/prettier': ['warn', prettierConfig],
},
globals: {
$docsify: 'writable',
Docsify: 'writable',
dom: 'writable',
},
};

View File

@ -13,6 +13,6 @@ let cover = read(file, 'utf8').toString();
console.log('Replace version number in cover page...');
cover = cover.replace(
/<small>(\S+)?<\/small>/g,
/* html */ `<small>${version}</small>`
/* html */ `<small>${version}</small>`,
);
write(file, cover);

View File

@ -9,7 +9,7 @@ const filePaths = {
'src',
'core',
'render',
'emoji-data.js'
'emoji-data.js',
),
};
@ -26,7 +26,7 @@ async function getEmojiData() {
// Remove base URL from emoji URLs
Object.entries(data).forEach(
([key, value]) => (data[key] = value.replace(baseURL, ''))
([key, value]) => (data[key] = value.replace(baseURL, '')),
);
console.info(`- Retrieved ${Object.keys(data).length} emoji entries`);
@ -41,7 +41,7 @@ function writeEmojiPage(emojiData) {
const isExistingPage = fs.existsSync(filePaths.emojiMarkdown);
const emojiPage =
(isExistingPage && fs.readFileSync(filePaths.emojiMarkdown, 'utf8')) ||
`<!-- START -->\n\n<!-- END -->`;
'<!-- START -->\n\n<!-- END -->';
const emojiRegEx = /(<!--\s*START.*-->\n)([\s\S]*)(\n<!--\s*END.*-->)/;
const emojiMatch = emojiPage.match(emojiRegEx);
const emojiMarkdownStart = emojiMatch[1].trim();
@ -51,20 +51,20 @@ function writeEmojiPage(emojiData) {
.reduce(
(preVal, curVal) =>
(preVal += `:${curVal}: ` + '`' + `:${curVal}:` + '`' + '\n\n'),
''
'',
)
.trim();
if (emojiMarkdown !== newEmojiMarkdown) {
const newEmojiPage = emojiPage.replace(
emojiMatch[0],
`${emojiMarkdownStart}\n\n${newEmojiMarkdown}\n\n${emojiMarkdownEnd}`
`${emojiMarkdownStart}\n\n${newEmojiMarkdown}\n\n${emojiMarkdownEnd}`,
);
fs.writeFileSync(filePaths.emojiMarkdown, newEmojiPage);
console.info(
`- ${!isExistingPage ? 'Created' : 'Updated'}: ${filePaths.emojiMarkdown}`
`- ${!isExistingPage ? 'Created' : 'Updated'}: ${filePaths.emojiMarkdown}`,
);
} else {
console.info(`- No changes: ${filePaths.emojiMarkdown}`);
@ -86,7 +86,7 @@ function writeEmojiJS(emojiData) {
fs.writeFileSync(filePaths.emojiJS, newEmojiJS);
console.info(
`- ${!isExistingPage ? 'Created' : 'Updated'}: ${filePaths.emojiJS}`
`- ${!isExistingPage ? 'Created' : 'Updated'}: ${filePaths.emojiJS}`,
);
} else {
console.info(`- No changes: ${filePaths.emojiJS}`);

View File

@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />

View File

@ -47,7 +47,7 @@ window.$docsify = {
return /* html */ `
<div class="mermaid">${mermaid.render(
'mermaid-svg-' + num++,
code
code,
)}</div>
`;
}

View File

@ -85,7 +85,7 @@ self.addEventListener('fetch', event => {
.then(resp => resp || fetched)
.catch(_ => {
/* eat any errors */
})
}),
);
// Update the cache with the version we fetched (only for ok status)
@ -93,11 +93,11 @@ self.addEventListener('fetch', event => {
Promise.all([fetchedCopy, caches.open(RUNTIME)])
.then(
([response, cache]) =>
response.ok && cache.put(event.request, response)
response.ok && cache.put(event.request, response),
)
.catch(_ => {
/* eat any errors */
})
}),
);
}
});

79
eslint.config.js Normal file
View File

@ -0,0 +1,79 @@
import eslintConfigPrettier from 'eslint-config-prettier';
import playwrightPlugin from 'eslint-plugin-playwright';
import jestPlugin from 'eslint-plugin-jest';
import globals from 'globals';
import js from '@eslint/js';
export default [
// Ignore (Must be first item in array)
{
ignores: [
// Directories
'_playwright-*',
'dist',
'docs',
'lib',
'node_modules',
// Files
'**/*.md',
'CHANGELOG.md',
'emoji-data.*',
'HISTORY.md',
],
},
// ESLint Recommended
js.configs.recommended,
// Configuration: Prettier
eslintConfigPrettier,
// All Files
{
languageOptions: {
globals: {
...globals.browser,
...globals.node,
$docsify: 'readonly',
Docsify: 'readonly',
},
},
rules: {
'array-callback-return': ['error'],
'block-scoped-var': ['error'],
curly: ['error'],
'dot-notation': ['error'],
eqeqeq: ['error'],
'no-implicit-coercion': ['error', { boolean: false }],
'no-implicit-globals': ['error'],
'no-loop-func': ['error'],
'no-return-assign': ['error'],
'no-template-curly-in-string': ['error'],
'no-unneeded-ternary': ['error'],
'no-unused-vars': ['error', { args: 'none' }],
'no-useless-computed-key': ['error'],
'no-useless-return': ['error'],
'no-var': ['error'],
'prefer-const': [
'error',
{
destructuring: 'all',
},
],
},
},
// Source Files
{
files: ['src/**'],
rules: {
'no-console': ['warn'],
},
},
// Tests: E2E (Playwright)
{
files: ['test/e2e/**'],
...playwrightPlugin.configs['flat/recommended'],
},
// Tests: Integration & Unit (Jest)
{
files: ['test/{integration,unit}/**'],
...jestPlugin.configs['flat/recommended'],
},
];

View File

@ -40,7 +40,7 @@ export default async function middleware(request) {
const indexHTML = await fetch(indexURL).then(res => res.text());
const previewHTML = rewriteRules.reduce(
(html, rule) => html.replace(rule.match, rule.replace),
indexHTML
indexHTML,
);
return new Response(previewHTML, {

1077
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -44,7 +44,7 @@
"devDependencies": {
"@babel/eslint-parser": "^7.24.5",
"@babel/preset-env": "^7.11.5",
"@eslint/js": "^8.43.0",
"@eslint/js": "^9.3.0",
"@playwright/test": "^1.44.0",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-commonjs": "^25.0.2",
@ -58,14 +58,13 @@
"conventional-changelog-cli": "^3.0.0",
"cross-env": "^7.0.3",
"cssnano": "^7.0.1",
"eslint": "^8.43.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.27.5",
"eslint": "^9.3.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-jest": "^28.5.0",
"eslint-plugin-playwright": "^1.6.1",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-prettier": "^5.1.3",
"glob": "^10.3.15",
"globals": "^13.20.0",
"globals": "^15.3.0",
"husky": "^9.0.11",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
@ -73,7 +72,7 @@
"marked": "^12.0.2",
"npm-run-all": "^4.1.5",
"postcss-cli": "^11.0.0",
"prettier": "^2.8.8",
"prettier": "^3.2.5",
"rimraf": "^5.0.7",
"rollup": "^4.17.2",
"stylus": "^0.63.0",
@ -98,7 +97,7 @@
"docker:test:integration": "npm run docker:cli -- test:integration",
"docker:test:unit": "npm run docker:cli -- test:unit",
"docker:test": "npm run docker:cli -- test",
"lint:fix": "eslint . --fix",
"lint:fix": "prettier . --write && eslint . --fix",
"lint": "prettier . --check && eslint .",
"postinstall": "opencollective-postinstall && npx husky install",
"prepare": "npm run build",

View File

@ -37,7 +37,7 @@ const pluginConfigs = pluginPaths.map(pluginPath => {
// =============================================================================
const currentYear = new Date().getFullYear();
const { homepage, license, version } = JSON.parse(
await fs.readFile(path.join(import.meta.dirname, 'package.json'), 'utf8')
await fs.readFile(path.join(import.meta.dirname, 'package.json'), 'utf8'),
);
const baseConfig = {
output: {

View File

@ -10,10 +10,8 @@ import { Lifecycle } from './init/lifecycle.js';
/** @typedef {new (...args: any[]) => any} Constructor */
// eslint-disable-next-line new-cap
export class Docsify extends Fetch(
// eslint-disable-next-line new-cap
Events(Render(VirtualRoutes(Router(Lifecycle(Object)))))
Events(Render(VirtualRoutes(Router(Lifecycle(Object))))),
) {
config = config(this);
@ -37,6 +35,8 @@ export class Docsify extends Fetch(
} catch (err) {
if (this.config.catchPluginErrors) {
const errTitle = 'Docsify plugin error';
// eslint-disable-next-line no-console
console.error(errTitle, err);
} else {
throw err;

View File

@ -49,6 +49,8 @@ export default function (vm) {
set themeColor(value) {
if (value) {
this.__themeColor = value;
// eslint-disable-next-line no-console
console.warn(
stripIndent(`
$docsify.themeColor is deprecated. Use a --theme-color property in your style sheet. Example:
@ -57,7 +59,7 @@ export default function (vm) {
--theme-color: deeppink;
}
</style>
`).trim()
`).trim(),
);
}
},
@ -65,7 +67,7 @@ export default function (vm) {
typeof window.$docsify === 'function'
? window.$docsify(vm)
: window.$docsify
: window.$docsify,
);
// Merge default and user-specified key bindings
@ -86,14 +88,14 @@ export default function (vm) {
},
},
// User-specified
config.keyBindings
config.keyBindings,
);
}
const script =
currentScript ||
Array.from(document.getElementsByTagName('script')).filter(n =>
/docsify\./.test(n.src)
/docsify\./.test(n.src),
)[0];
if (script) {

View File

@ -27,7 +27,7 @@ export function Events(Base) {
if (topMargin) {
document.documentElement.style.setProperty(
'scroll-padding-top',
`${topMargin}px`
`${topMargin}px`,
);
}
@ -93,7 +93,7 @@ export function Events(Base) {
a.compareDocumentPosition(b) &
Node.DOCUMENT_POSITION_FOLLOWING
? -1
: 1
: 1,
)[0]
: // Get first and only item in set.
// May be undefined if no headings are in view.
@ -115,7 +115,7 @@ export function Events(Base) {
},
{
rootMargin: '0% 0% -50% 0%', // Top half of viewport
}
},
);
headingElms.forEach(elm => {
@ -172,7 +172,7 @@ export function Events(Base) {
// Handle keyboard events
dom.on('keydown', e => {
const isTextEntry = document.activeElement.matches(
'input, select, textarea'
'input, select, textarea',
);
if (isTextEntry) {
@ -192,9 +192,9 @@ export function Events(Base) {
(modifierKeys.includes(k) && e[k + 'Key']) ||
e.key === k || // Ex: " ", "a"
e.code.toLowerCase() === k || // "space"
e.code.toLowerCase() === `key${k}` // "keya"
)
)
e.code.toLowerCase() === `key${k}`, // "keya"
),
),
);
matchingConfigs.forEach(({ callback }) => {
@ -262,7 +262,7 @@ export function Events(Base) {
dom.on(
dom.body,
'click',
() => dom.body.classList.contains('close') && toggle()
() => dom.body.classList.contains('close') && toggle(),
);
}
@ -297,7 +297,7 @@ export function Events(Base) {
onRender() {
const currentPath = this.router.toURL(this.router.getCurrentPath());
const currentTitle = dom.find(
`.sidebar a[href='${currentPath}']`
`.sidebar a[href='${currentPath}']`,
)?.innerText;
// Update page title
@ -327,7 +327,7 @@ export function Events(Base) {
// Anchor link
if (query.id) {
const headingElm = dom.find(
`.markdown-section :where(h1, h2, h3, h4, h5)[id="${query.id}"]`
`.markdown-section :where(h1, h2, h3, h4, h5)[id="${query.id}"]`,
);
if (headingElm) {
@ -402,7 +402,7 @@ export function Events(Base) {
.find(
a =>
href.includes(a.getAttribute('href')) ||
href.includes(decodeURI(a.getAttribute('href')))
href.includes(decodeURI(a.getAttribute('href'))),
);
const oldActive = dom.find(navElm, 'li.active');
@ -434,7 +434,7 @@ export function Events(Base) {
const newActive = dom
.find(
sidebar,
`a[href="${href}"], a[href="${decodeURIComponent(href)}"]`
`a[href="${href}"], a[href="${decodeURIComponent(href)}"]`,
)
?.closest('li');
@ -467,7 +467,7 @@ export function Events(Base) {
const newPage = dom
.find(
sidebar,
`a[href="${path}"], a[href="${decodeURIComponent(path)}"]`
`a[href="${path}"], a[href="${decodeURIComponent(path)}"]`,
)
?.closest('li');
@ -496,7 +496,7 @@ export function Events(Base) {
document.addEventListener(
'scrollend',
() => (this.#isScrolling = false),
{ once: true }
{ once: true },
);
}
// Browsers w/o native scrollend event support (Safari)
@ -515,7 +515,7 @@ export function Events(Base) {
document.addEventListener('scroll', callback, false);
}
},
{ once: true }
{ once: true },
);
}
};

View File

@ -22,7 +22,7 @@ export function Fetch(Base) {
get(
vm.router.getFile(path + file) + qs,
false,
vm.config.requestHeaders
vm.config.requestHeaders,
).then(next, _error => this.#loadNested(path, qs, file, next, vm));
}
@ -83,7 +83,7 @@ export function Fetch(Base) {
_fetch(cb = noop) {
const { query } = this.route;
let { path } = this.route;
const { path } = this.route;
// Prevent loading remote content via URL hash
// Ex: https://foo.com/#//bar.com/file.md
@ -107,7 +107,7 @@ export function Fetch(Base) {
this._renderMain(
text,
opt,
this._loadSideAndNav(path, qs, loadSidebar, cb)
this._loadSideAndNav(path, qs, loadSidebar, cb),
);
};
@ -125,7 +125,7 @@ export function Fetch(Base) {
} else {
this.#request(file + qs, requestHeaders).then(
contentFetched,
contentFailedToFetch
contentFailedToFetch,
);
}
});
@ -133,7 +133,7 @@ export function Fetch(Base) {
// if the requested url is not local, just fetch the file
this.#request(file + qs, requestHeaders).then(
contentFetched,
contentFailedToFetch
contentFailedToFetch,
);
}
@ -145,7 +145,7 @@ export function Fetch(Base) {
loadNavbar,
text => this._renderNav(text),
this,
true
true,
);
}
}
@ -174,7 +174,7 @@ export function Fetch(Base) {
path = this.router.getFile(root + path);
this.coverIsHTML = /\.html$/g.test(path);
get(path + stringifyQuery(query, ['id']), false, requestHeaders).then(
text => this._renderCover(text, coverOnly)
text => this._renderCover(text, coverOnly),
);
} else {
this._renderCover(null, coverOnly);
@ -216,7 +216,7 @@ export function Fetch(Base) {
}
const newPath = this.router.getFile(
path.replace(new RegExp(`^/${local}`), '')
path.replace(new RegExp(`^/${local}`), ''),
);
const req = this.#request(newPath + qs, requestHeaders);
@ -225,9 +225,9 @@ export function Fetch(Base) {
this._renderMain(
text,
opt,
this._loadSideAndNav(path, qs, loadSidebar, cb)
this._loadSideAndNav(path, qs, loadSidebar, cb),
),
_error => this._fetch404(path, qs, cb)
_error => this._fetch404(path, qs, cb),
);
return true;
@ -250,7 +250,7 @@ export function Fetch(Base) {
this.#request(this.router.getFile(path404), requestHeaders).then(
(text, opt) => this._renderMain(text, opt, fnLoadSideAndNav),
_error => this._renderMain(null, {}, fnLoadSideAndNav)
_error => this._renderMain(null, {}, fnLoadSideAndNav),
);
return true;
}

View File

@ -47,6 +47,7 @@ export function Lifecycle(Base) {
});
} catch (err) {
if (catchPluginErrors) {
// eslint-disable-next-line no-console
console.error(errTitle, err);
} else {
throw err;
@ -62,6 +63,7 @@ export function Lifecycle(Base) {
step(index + 1);
} catch (err) {
if (catchPluginErrors) {
// eslint-disable-next-line no-console
console.error(errTitle, err);
} else {
throw err;

View File

@ -85,7 +85,7 @@ export class Compiler {
marked.setOptions(
Object.assign(mdConf, {
renderer: Object.assign(renderer, mdConf.renderer),
})
}),
);
compile = marked;
}
@ -152,7 +152,7 @@ export class Compiler {
href = getPath(
this.contentBase,
getParentPath(this.router.getCurrentPath()),
href
href,
);
}
@ -282,7 +282,7 @@ export class Compiler {
}
const tree = this.cacheTree[currentPath] || genTree(toc, level);
html = treeTpl(tree, /* html */ `<ul>{inner}</ul>`);
html = treeTpl(tree, /* html */ '<ul>{inner}</ul>');
this.cacheTree[currentPath] = tree;
}

View File

@ -8,7 +8,7 @@ export const highlightCodeCompiler = ({ renderer }) =>
const text = Prism.highlight(
code.replace(/@DOCSIFY_QM@/g, '`'),
langOrMarkup,
lang
lang,
);
return /* html */ `<pre data-lang="${lang}"><code class="lang-${lang}" tabindex="0">${text}</code></pre>`;

View File

@ -4,7 +4,7 @@ import { isAbsolutePath, getPath, getParentPath } from '../../router/util.js';
export const imageCompiler = ({ renderer, contentBase, router }) =>
(renderer.image = (href, title, text) => {
let url = href;
let attrs = [];
const attrs = [];
const { str, config } = getAndRemoveConfig(title);
title = str;
@ -39,6 +39,6 @@ export const imageCompiler = ({ renderer, contentBase, router }) =>
}
return /* html */ `<img src="${url}" data-origin="${href}" alt="${text}" ${attrs.join(
' '
' ',
)} />`;
});

View File

@ -9,7 +9,7 @@ export const linkCompiler = ({
compilerClass,
}) =>
(renderer.link = (href, title = '', text) => {
let attrs = [];
const attrs = [];
const { str, config } = getAndRemoveConfig(title);
linkTarget = config.target || linkTarget;
linkRel =
@ -42,8 +42,8 @@ export const linkCompiler = ({
href.indexOf('mailto:') === 0
? ''
: linkRel !== ''
? ` rel="${linkRel}"`
: ''
? ` rel="${linkRel}"`
: '',
);
}

View File

@ -1,7 +1,7 @@
export const taskListCompiler = ({ renderer }) =>
(renderer.list = (body, ordered, start) => {
const isTaskList = /<li class="task-list-item">/.test(
body.split('class="task-list"')[0]
body.split('class="task-list"')[0],
);
const isStartReq = start && start > 1;
const tag = ordered ? 'ol' : 'ul';

View File

@ -15,6 +15,7 @@ function walkFetchEmbed({ embedTokens, compile, fetch }, cb) {
while ((token = embedTokens[step++])) {
const currentToken = token;
// eslint-disable-next-line no-loop-func
const next = text => {
let embedToken;
if (text) {
@ -48,7 +49,7 @@ function walkFetchEmbed({ embedTokens, compile, fetch }, cb) {
if (currentToken.embed.fragment) {
const fragment = currentToken.embed.fragment;
const pattern = new RegExp(
`(?:###|\\/\\/\\/)\\s*\\[${fragment}\\]([\\s\\S]*)(?:###|\\/\\/\\/)\\s*\\[${fragment}\\]`
`(?:###|\\/\\/\\/)\\s*\\[${fragment}\\]([\\s\\S]*)(?:###|\\/\\/\\/)\\s*\\[${fragment}\\]`,
);
text = stripIndent((text.match(pattern) || [])[1] || '').trim();
}
@ -58,7 +59,7 @@ function walkFetchEmbed({ embedTokens, compile, fetch }, cb) {
currentToken.embed.lang +
'\n' +
text.replace(/`/g, '@DOCSIFY_QM@') +
'\n```\n'
'\n```\n',
);
} else if (currentToken.embed.type === 'mermaid') {
embedToken = [
@ -89,7 +90,7 @@ function walkFetchEmbed({ embedTokens, compile, fetch }, cb) {
}
export function prerenderEmbed({ compiler, raw = '', fetch }, done) {
let hit = cached[raw];
const hit = cached[raw];
if (hit) {
const copy = hit.slice();
copy.links = hit.links;
@ -117,7 +118,7 @@ export function prerenderEmbed({ compiler, raw = '', fetch }, done) {
}
return src;
}
},
);
}
});

View File

@ -1,4 +1,4 @@
/* eslint-disable */
// =============================================================================
// DO NOT EDIT: This file is auto-generated by an /build/emoji.js

View File

@ -31,17 +31,17 @@ export function emojify(text, useNativeEmoji) {
// Mark colons in tags
.replace(
/<(code|pre|script|template)[^>]*?>[\s\S]+?<\/(code|pre|script|template)>/g,
m => m.replace(/:/g, '__colon__')
m => m.replace(/:/g, '__colon__'),
)
// Mark colons in comments
.replace(/<!--[\s\S]+?-->/g, m => m.replace(/:/g, '__colon__'))
// Mark colons in URIs
.replace(/([a-z]{2,}:)?\/\/[^\s'">)]+/gi, m =>
m.replace(/:/g, '__colon__')
m.replace(/:/g, '__colon__'),
)
// Replace emoji shorthand codes
.replace(/:([a-z0-9_\-+]+?):/g, (m, $1) =>
replaceEmojiShorthand(m, $1, useNativeEmoji)
replaceEmojiShorthand(m, $1, useNativeEmoji),
)
// Restore colons in tags and comments
.replace(/__colon__/g, ':')

View File

@ -1,4 +1,3 @@
/* eslint-disable no-unused-vars */
import tinydate from 'tinydate';
import * as dom from '../util/dom.js';
import { getPath, isAbsolutePath } from '../router/util.js';
@ -47,8 +46,8 @@ export function Render(Base) {
typeof fn === 'function'
? fn(updated)
: typeof fn === 'string'
? tinydate(fn)(new Date(updated))
: updated;
? tinydate(fn)(new Date(updated))
: updated;
return html.replace(/{docsify-updated}/g, updated);
}
@ -101,7 +100,7 @@ export function Render(Base) {
const vueGlobalOptions = docsifyConfig.vueGlobalOptions || {};
const vueMountData = [];
const vueComponentNames = Object.keys(
docsifyConfig.vueComponents || {}
docsifyConfig.vueComponents || {},
);
// Register global vueComponents
@ -131,7 +130,7 @@ export function Render(Base) {
dom.find(markdownElm, cssSelector),
docsifyConfig.vueMounts[cssSelector],
])
.filter(([elm, vueConfig]) => elm)
.filter(([elm, vueConfig]) => elm),
);
// Template syntax, vueComponents, vueGlobalOptions ...
@ -189,7 +188,7 @@ export function Render(Base) {
}
return [elm, vueConfig];
})
}),
);
// Not found mounts but import Vue resource
@ -244,7 +243,7 @@ export function Render(Base) {
el.setAttribute('href', nameLink);
} else if (typeof nameLink === 'object') {
const match = Object.keys(nameLink).filter(
key => path.indexOf(key) > -1
key => path.indexOf(key) > -1,
)[0];
el.setAttribute('href', nameLink[match]);
@ -262,7 +261,7 @@ export function Render(Base) {
if (skipLink?.constructor === Object) {
const matchingPath = Object.keys(skipLink).find(path =>
vm.route.path.startsWith(path.startsWith('/') ? path : `/${path}`)
vm.route.path.startsWith(path.startsWith('/') ? path : `/${path}`),
);
const matchingText = matchingPath && skipLink[matchingPath];
@ -355,7 +354,7 @@ export function Render(Base) {
html = this.#formatUpdated(
html,
opt.updatedAt,
this.config.formatUpdated
this.config.formatUpdated,
);
}
@ -377,7 +376,7 @@ export function Render(Base) {
tokens => {
html = this.compiler.compile(tokens);
callback();
}
},
);
}
});
@ -389,7 +388,7 @@ export function Render(Base) {
dom.toggleClass(
dom.getNode('main'),
coverOnly ? 'add' : 'remove',
'hidden'
'hidden',
);
if (!text) {
dom.toggleClass(el, 'remove', 'show');
@ -440,7 +439,6 @@ export function Render(Base) {
// Init markdown compiler
this.compiler = new Compiler(config, this.router);
if (inBrowser) {
/* eslint-disable-next-line camelcase */
window.__current_docsify_compiler__ = this.compiler;
}
@ -494,7 +492,7 @@ export function Render(Base) {
if (config.themeColor) {
dom.$.head.appendChild(
dom.create('div', tpl.theme(config.themeColor)).firstElementChild
dom.create('div', tpl.theme(config.themeColor)).firstElementChild,
);
}

View File

@ -41,7 +41,7 @@ export default function (info) {
if (num >= 95) {
clearTimeout(timeId);
// eslint-disable-next-line no-unused-vars
timeId = setTimeout(_ => {
barEl.style.opacity = 0;
barEl.style.width = '0%';

View File

@ -95,7 +95,7 @@ export function cover() {
*/
export function tree(
toc,
tpl = /* html */ `<ul class="app-sub-sidebar">{inner}</ul>`
tpl = /* html */ '<ul class="app-sub-sidebar">{inner}</ul>',
) {
if (!toc || !toc.length) {
return '';

View File

@ -26,7 +26,7 @@ export class History {
? this.#getAlias(
path.replace(this.#cached[match], alias[match]),
alias,
path
path,
)
: path;
}
@ -35,8 +35,8 @@ export class History {
return new RegExp(`\\.(${ext.replace(/^\./, '')}|html)$`, 'g').test(path)
? path
: /\/$/g.test(path)
? `${path}README${ext}`
: `${path}${ext}`;
? `${path}README${ext}`
: `${path}${ext}`;
}
getBasePath() {
@ -88,7 +88,7 @@ export class History {
if (this.config.relativePath && path.indexOf('/') !== 0) {
const currentDir = currentRoute.substring(
0,
currentRoute.lastIndexOf('/') + 1
currentRoute.lastIndexOf('/') + 1,
);
return cleanPath(resolvePath(currentDir + path));
}

View File

@ -43,7 +43,6 @@ export function Router(Base) {
this.updateRender();
lastRoute = this.route;
// eslint-disable-next-line no-unused-vars
router.onchange(params => {
this.updateRender();
this._updateRender();

View File

@ -33,7 +33,7 @@ export function stringifyQuery(obj, ignores = []) {
qs.push(
obj[key]
? `${encode(key)}=${encode(obj[key])}`.toLowerCase()
: encode(key)
: encode(key),
);
}
@ -63,7 +63,7 @@ export const cleanPath = cached(path => {
export const resolvePath = cached(path => {
const segments = path.replace(/^\//, '').split('/');
let resolved = [];
const resolved = [];
for (const segment of segments) {
if (segment === '..') {
resolved.pop();

View File

@ -1,5 +1,5 @@
// @ts-check
/* eslint-disable no-unused-vars */
import progressbar from '../render/progressbar.js';
import { noop } from './core.js';
@ -53,7 +53,7 @@ export function get(url, hasBar = false, headers = {}) {
progressbar({
step: Math.floor(Math.random() * 5 + 1),
}),
500
500,
);
xhr.addEventListener('progress', progressbar);

View File

@ -52,8 +52,8 @@ export function isFn(obj) {
* @returns {Boolean} True if the passed-in url is external
*/
export function isExternal(url) {
let match = url.match(
/^([^:/?#]+:)?(?:\/{2,}([^/?#]*))?([^?#]+)?(\?[^#]*)?(#.*)?/
const match = url.match(
/^([^:/?#]+:)?(?:\/{2,}([^/?#]*))?([^?#]+)?(\?[^#]*)?(#.*)?/,
);
if (
@ -68,9 +68,9 @@ export function isExternal(url) {
match[2].length > 0 &&
match[2].replace(
new RegExp(
':(' + { 'http:': 80, 'https:': 443 }[location.protocol] + ')?$'
':(' + { 'http:': 80, 'https:': 443 }[location.protocol] + ')?$',
),
''
'',
) !== location.host
) {
return true;

View File

@ -26,7 +26,6 @@ function install(hook, vm) {
div.style = `width: ${main.clientWidth}px; margin: 0 auto 20px;`;
dom.appendTo(dom.find('.content'), div);
// eslint-disable-next-line
window.disqus_config = function () {
this.page.url = location.origin + '/-' + vm.route.path;
this.page.identifier = vm.route.path;

View File

@ -2,6 +2,7 @@ import emojiData from '../core/render/emoji-data.js';
// Deprecation notice
if (window && window.console) {
// eslint-disable-next-line no-console
console.info('Docsify emoji plugin has been deprecated as of v4.13');
}

View File

@ -47,7 +47,7 @@ var errors = [],
item: new RegExp('^-\\s+'),
trim: new RegExp('^\\s+|\\s+$'),
comment: new RegExp(
'([^\\\'\\"#]+([\\\'\\"][^\\\'\\"]*[\\\'\\"])*)*(#.*)?'
'([^\\\'\\"#]+([\\\'\\"][^\\\'\\"]*[\\\'\\"])*)*(#.*)?',
),
};

View File

@ -29,6 +29,7 @@ function collect() {
const install = function (hook) {
if (!$docsify.ga) {
// eslint-disable-next-line no-console
console.error('[Docsify] ga is required.');
return;
}

View File

@ -51,16 +51,15 @@ function collect() {
// usage: https://developers.google.com/analytics/devguides/collection/gtagjs/pages
window.gtag('event', 'page_view', {
/* eslint-disable camelcase */
page_title: document.title,
page_location: location.href,
page_path: location.pathname,
/* eslint-disable camelcase */
});
}
const install = function (hook) {
if (!$docsify.gtag) {
// eslint-disable-next-line no-console
console.error('[Docsify] gtag is required.');
return;
}

View File

@ -1,4 +1,3 @@
/* eslint-disable no-unused-vars */
import { search } from './search.js';
let NO_DATA_TEXT = '';
@ -216,7 +215,7 @@ function bindEvents() {
'click',
e =>
['A', 'H2', 'P', 'EM'].indexOf(e.target.tagName) === -1 &&
e.stopPropagation()
e.stopPropagation(),
);
Docsify.dom.on($input, 'input', e => {
clearTimeout(timeId);

View File

@ -1,4 +1,3 @@
/* eslint-disable no-unused-vars */
import {
init as initComponent,
update as updateComponent,

View File

@ -1,4 +1,3 @@
/* eslint-disable no-unused-vars */
import {
getAndRemoveConfig,
getAndRemoveDocisfyIgnoreConfig,
@ -178,9 +177,9 @@ export function search(query) {
const regEx = new RegExp(
escapeHtml(ignoreDiacriticalMarks(keyword)).replace(
/[|\\{}()[\]^$+*?.]/g,
'\\$&'
'\\$&',
),
'gi'
'gi',
);
let indexTitle = -1;
let indexContent = -1;
@ -217,7 +216,7 @@ export function search(query) {
.substring(start, end)
.replace(
regEx,
word => /* html */ `<em class="search-keyword">${word}</em>`
word => /* html */ `<em class="search-keyword">${word}</em>`,
) +
'...';
@ -254,7 +253,7 @@ export function init(config, vm) {
if (Array.isArray(config.pathNamespaces)) {
namespaceSuffix =
config.pathNamespaces.filter(
prefix => path.slice(0, prefix.length) === prefix
prefix => path.slice(0, prefix.length) === prefix,
)[0] || namespaceSuffix;
} else if (config.pathNamespaces instanceof RegExp) {
const matches = path.match(config.pathNamespaces);
@ -297,7 +296,7 @@ export function init(config, vm) {
result => {
INDEXS[path] = genIndex(path, result, vm.router, config.depth);
len === ++count && saveData(config.maxAge, expireKey, indexKey);
}
},
);
});
}

View File

@ -6,8 +6,8 @@ function install(hook) {
hook.doneEach(_ => {
let elms = Array.from(
document.querySelectorAll(
'.markdown-section img:not(.emoji):not([data-no-zoom])'
)
'.markdown-section img:not(.emoji):not([data-no-zoom])',
),
);
elms = elms.filter(elm => !elm.matches('a img'));

View File

@ -110,7 +110,7 @@ beforeEach(async () => {
// Mock IntersectionObserver
// -----------------------------------------------------------------------------
[global, window].forEach(
obj => (obj.IntersectionObserver = IntersectionObserver)
obj => (obj.IntersectionObserver = IntersectionObserver),
);
});

View File

@ -17,7 +17,7 @@ export async function startServer() {
// problematic for testing and CI/CD.
if (bsServer.getOption('port') !== settings.port) {
console.log(
`\nPort ${settings.port} not available. Exiting process.\n`
`\nPort ${settings.port} not available. Exiting process.\n`,
);
process.exit(0);
}

View File

@ -1,3 +0,0 @@
module.exports = {
extends: ['plugin:playwright/playwright-test'],
};

View File

@ -21,7 +21,7 @@ test.describe('Creating a Docsify site (e2e tests in Playwright)', () => {
await page.addScriptTag({ url: '/dist/docsify.js' });
// Wait for docsify to initialize
await page.waitForSelector('#main');
await page.locator('#main').waitFor();
// Create handle for JavaScript object in browser
const $docsify = await page.evaluate(() => window.$docsify);
@ -42,7 +42,7 @@ test.describe('Creating a Docsify site (e2e tests in Playwright)', () => {
const mainElm = page.locator('#main');
await expect(mainElm).toHaveCount(1);
await expect(mainElm).toContainText(
'A magical documentation site generator'
'A magical documentation site generator',
);
});
@ -128,11 +128,14 @@ test.describe('Creating a Docsify site (e2e tests in Playwright)', () => {
// Verify docsifyInitConfig.script was added to the DOM
expect(
await page.evaluate(scriptText => {
return [...document.querySelectorAll('script')].some(
elm => elm.textContent.replace(/\s+/g, '') === scriptText
);
}, docsifyInitConfig.script.replace(/\s+/g, ''))
await page.evaluate(
scriptText => {
return [...document.querySelectorAll('script')].some(
elm => elm.textContent.replace(/\s+/g, '') === scriptText,
);
},
docsifyInitConfig.script.replace(/\s+/g, ''),
),
).toBe(true);
// Verify docsifyInitConfig.script was executed
@ -141,17 +144,20 @@ test.describe('Creating a Docsify site (e2e tests in Playwright)', () => {
// Verify docsifyInitConfig.styleURLs were added to the DOM
for (const styleURL of docsifyInitConfig.styleURLs) {
await expect(
page.locator(`link[rel*="stylesheet"][href$="${styleURL}"]`)
page.locator(`link[rel*="stylesheet"][href$="${styleURL}"]`),
).toHaveCount(1);
}
// Verify docsifyInitConfig.style was added to the DOM
expect(
await page.evaluate(styleText => {
return [...document.querySelectorAll('style')].some(
elm => elm.textContent.replace(/\s+/g, '') === styleText
);
}, docsifyInitConfig.style.replace(/\s+/g, ''))
await page.evaluate(
styleText => {
return [...document.querySelectorAll('style')].some(
elm => elm.textContent.replace(/\s+/g, '') === styleText,
);
},
docsifyInitConfig.style.replace(/\s+/g, ''),
),
).toBe(true);
// Verify docsify navigation and docsifyInitConfig.routes

View File

@ -12,7 +12,7 @@ test.describe('Index file hosting', () => {
test('should serve from index file', async ({ page }) => {
await docsifyInit(sharedOptions);
await expect(page.locator('#main')).toContainText(
'A magical documentation site generator'
'A magical documentation site generator',
);
expect(page.url()).toMatch(/index\.html#\/$/);
});

View File

@ -12,7 +12,7 @@ test.describe('Security - Cross Site Scripting (XSS)', () => {
};
const slashStrings = ['//', '///'];
for (let slashString of slashStrings) {
for (const slashString of slashStrings) {
const hash = `#${slashString}domain.com/file.md`;
test(`should not load remote content from hash (${hash})`, async ({

View File

@ -8,9 +8,7 @@ import { test, expect } from './fixtures/docsify-init-fixture.js';
*/
async function navigateToRoute(page, route) {
await page.evaluate(r => (window.location.hash = r), route);
// TODO: playwright eslint now recommends not using networkidle
// eslint-disable-next-line
await page.waitForLoadState('networkidle');
await page.waitForLoadState('load');
}
test.describe('Virtual Routes - Generate Dynamic Content via Config', () => {
@ -60,7 +58,7 @@ test.describe('Virtual Routes - Generate Dynamic Content via Config', () => {
'/my-awesome-async-function-route': async function (
route,
matched,
next
next,
) {
setTimeout(() => next('# My Awesome Function Route'), 100);
},
@ -101,7 +99,7 @@ test.describe('Virtual Routes - Generate Dynamic Content via Config', () => {
page,
}) => {
const routes = {
'/pets/(.*)': route => `# Route: /pets/dog`,
'/pets/(.*)': route => '# Route: /pets/dog',
};
await docsifyInit({

View File

@ -83,7 +83,7 @@ test.describe('Vue.js Compatibility', () => {
// Tests
// ----------------------------------------------------------------------------
test(`Parse templates and render content when import Vue resources`, async ({
test('Parse templates and render content when import Vue resources', async ({
page,
}) => {
const docsifyInitConfig = {
@ -116,7 +116,7 @@ test.describe('Vue.js Compatibility', () => {
await expect(page.locator('#vuefor')).toHaveText('12345');
await expect(page.locator('#vuecomponent')).toHaveText('0');
await expect(page.locator('#vueglobaloptions p')).toHaveText(
'vueglobaloptions'
'vueglobaloptions',
);
await expect(page.locator('#vueglobaloptions > span')).toHaveText('0');
await expect(page.locator('#vuemounts p')).toHaveText('vuemounts');
@ -136,7 +136,7 @@ test.describe('Vue.js Compatibility', () => {
});
}
test(`ignores content when Vue is not present`, async ({ page }) => {
test('ignores content when Vue is not present', async ({ page }) => {
const docsifyInitConfig = getSharedConfig();
await docsifyInit(docsifyInitConfig);
@ -148,7 +148,7 @@ test.describe('Vue.js Compatibility', () => {
await expect(page.locator('#vuescript p')).toHaveText('---');
});
test(`ignores content when vueGlobalOptions is undefined`, async ({
test('ignores content when vueGlobalOptions is undefined', async ({
page,
}) => {
const docsifyInitConfig = getSharedConfig();
@ -166,7 +166,7 @@ test.describe('Vue.js Compatibility', () => {
await expect(page.locator('#vuescript p')).toHaveText('vuescript');
});
test(`ignores content when vueMounts is undefined`, async ({ page }) => {
test('ignores content when vueMounts is undefined', async ({ page }) => {
const docsifyInitConfig = getSharedConfig();
docsifyInitConfig.config.vueMounts['#vuemounts'] = undefined;
@ -176,13 +176,13 @@ test.describe('Vue.js Compatibility', () => {
await expect(page.locator('#vuefor')).toHaveText('12345');
await expect(page.locator('#vuecomponent')).toHaveText('0');
await expect(page.locator('#vueglobaloptions p')).toHaveText(
'vueglobaloptions'
'vueglobaloptions',
);
await expect(page.locator('#vuemounts p')).toHaveText('vueglobaloptions');
await expect(page.locator('#vuescript p')).toHaveText('vuescript');
});
test(`ignores <script> when executeScript is false`, async ({ page }) => {
test('ignores <script> when executeScript is false', async ({ page }) => {
const docsifyInitConfig = getSharedConfig();
docsifyInitConfig.config.executeScript = false;

View File

@ -65,7 +65,7 @@ async function docsifyInit(options = {}) {
style: '',
styleURLs: [],
testURL: `${process.env.TEST_HOST}/docsify-init.html`,
waitForSelector: '#main > *',
waitForSelector: '#main > *:first-child',
};
const settings = {
...defaults,
@ -83,7 +83,7 @@ async function docsifyInit(options = {}) {
if (config.basePath) {
config.basePath = new URL(
config.basePath,
process.env.TEST_HOST
process.env.TEST_HOST,
).href;
}
};
@ -114,16 +114,16 @@ async function docsifyInit(options = {}) {
...options.markdown,
})
.filter(([key, markdown]) => key && markdown)
.map(([key, markdown]) => [key, stripIndent`${markdown}`])
.map(([key, markdown]) => [key, stripIndent`${markdown}`]),
);
},
get routes() {
const helperRoutes = {
[settings.testURL]: stripIndent`${settings.html}`,
['README.md']: settings.markdown.homepage,
['_coverpage.md']: settings.markdown.coverpage,
['_navbar.md']: settings.markdown.navbar,
['_sidebar.md']: settings.markdown.sidebar,
'README.md': settings.markdown.homepage,
'_coverpage.md': settings.markdown.coverpage,
'_navbar.md': settings.markdown.navbar,
'_sidebar.md': settings.markdown.sidebar,
};
const finalRoutes = Object.fromEntries(
@ -139,7 +139,7 @@ async function docsifyInit(options = {}) {
.href,
// Strip indentation from responseText
stripIndent`${responseText}`,
])
]),
);
return finalRoutes;
@ -165,7 +165,7 @@ async function docsifyInit(options = {}) {
};
const reFileExtentionFromURL = new RegExp(
'(?:.)(' + Object.keys(contentTypes).join('|') + ')(?:[?#].*)?$',
'i'
'i',
);
if (isJSDOM) {
@ -219,7 +219,7 @@ async function docsifyInit(options = {}) {
} else if (isPlaywright) {
// Convert config functions to strings
const configString = JSON.stringify(settings.config, (key, val) =>
typeof val === 'function' ? `__FN__${val.toString()}` : val
typeof val === 'function' ? `__FN__${val.toString()}` : val,
);
await page.evaluate(config => {
@ -303,13 +303,16 @@ async function docsifyInit(options = {}) {
styleElm.textContent = stripIndent`${settings.style}`;
headElm.appendChild(styleElm);
} else if (isPlaywright) {
await page.evaluate(data => {
const headElm = document.querySelector('head');
const styleElm = document.createElement('style');
await page.evaluate(
data => {
const headElm = document.querySelector('head');
const styleElm = document.createElement('style');
styleElm.textContent = data;
headElm.appendChild(styleElm);
}, stripIndent`${settings.style}`);
styleElm.textContent = data;
headElm.appendChild(styleElm);
},
stripIndent`${settings.style}`,
);
}
}
@ -329,8 +332,9 @@ async function docsifyInit(options = {}) {
if (isJSDOM) {
await waitForSelector(settings.waitForSelector);
} else if (isPlaywright) {
await page.waitForSelector(settings.waitForSelector);
await page.waitForLoadState('networkidle');
// await page.waitForSelector(settings.waitForSelector);
await page.locator(settings.waitForSelector).waitFor();
await page.waitForLoadState('load');
}
// Log HTML to console
@ -345,11 +349,11 @@ async function docsifyInit(options = {}) {
if (selector) {
if (isJSDOM) {
htmlArr = [...document.querySelectorAll(selector)].map(
elm => elm.outerHTML
elm => elm.outerHTML,
);
} else {
htmlArr = await page.evaluateAll(selector, elms =>
elms.map(e => e.outerHTML)
elms.map(e => e.outerHTML),
);
}
} else {
@ -364,11 +368,9 @@ async function docsifyInit(options = {}) {
html = prettier.format(html, { parser: 'html' });
}
// eslint-disable-next-line no-console
console.log(html);
});
} else {
// eslint-disable-next-line no-console
console.warn(`docsify-init(): unable to match selector '${selector}'`);
}
}

View File

@ -9,7 +9,10 @@ const defaults = {
* @param {Function} fn function to be evaluated until truthy
* @param {*} arg optional argument to pass to `fn`
* @param {Object} options optional parameters
* @returns {Promise} promise which resolves to function result
* @param {number} options.delay delay between fn invocations
* @param {number} options.timeout timeout in milliseconds
* @returns {Promise} promise which resolves to the truthy fn return value or
* rejects to an error object or last non-truthy fn return value
*/
function waitForFunction(fn, arg, options = {}) {
const settings = {
@ -19,14 +22,15 @@ function waitForFunction(fn, arg, options = {}) {
return new Promise((resolve, reject) => {
let timeElapsed = 0;
let lastError;
const int = setInterval(() => {
let result;
try {
result = fn(arg);
} catch (e) {
// Continue...
} catch (err) {
lastError = err;
}
if (result) {
@ -37,11 +41,10 @@ function waitForFunction(fn, arg, options = {}) {
timeElapsed += settings.delay;
if (timeElapsed >= settings.timeout) {
const msg = `waitForFunction did not return a truthy value (${
settings.timeout
} ms): ${fn.toString()}\n`;
reject(msg);
console.error(
`\nwaitForFunction did not return a truthy value within ${settings.timeout} ms.\n`,
);
reject(lastError || result);
}
}, settings.delay);
});
@ -52,6 +55,8 @@ function waitForFunction(fn, arg, options = {}) {
*
* @param {String} cssSelector CSS selector to query for
* @param {Object} options optional parameters
* @param {number} options.delay delay between checks
* @param {number} options.timeout timeout in milliseconds
* @returns {Promise} promise which resolves to first matching element
*/
function waitForSelector(cssSelector, options = {}) {
@ -87,6 +92,8 @@ function waitForSelector(cssSelector, options = {}) {
* @param {String} cssSelector CSS selector to query for
* @param {String} text text to match
* @param {Object} options optional parameters
* @param {number} options.delay delay between checks
* @param {number} options.timeout timeout in milliseconds
* @returns {Promise} promise which resolves to first matching element that contains specified text
*/
function waitForText(cssSelector, text, options = {}) {

View File

@ -1 +0,0 @@
module.exports = require('../unit/.eslintrc.cjs');

View File

@ -137,7 +137,8 @@ describe('Emoji', function () {
test('Ignores emoji shorthand codes in html attributes', async () => {
await docsifyInit({
markdown: {
homepage: /* html */ `<a href="http://domain.com/:smile:/"> <img src='http://domain.com/:smile:/file.png'> <script src=http://domain.com/:smile:/file.js></script>`,
homepage:
/* html */ '<a href="http://domain.com/:smile:/"> <img src=\'http://domain.com/:smile:/file.png\'> <script src=http://domain.com/:smile:/file.js></script>',
},
// _logHTML: true,
});
@ -150,7 +151,8 @@ describe('Emoji', function () {
test('Ignores emoji shorthand codes in style url() values', async () => {
await docsifyInit({
markdown: {
homepage: /* html */ `<style>@import url(http://domain.com/:smile/file.css);</style>`,
homepage:
/* html */ '<style>@import url(http://domain.com/:smile/file.css);</style>',
},
// _logHTML: true,
});

View File

@ -12,7 +12,7 @@ describe('Creating a Docsify site (integration tests in Jest)', function () {
// Verify options.markdown content was rendered
expect(document.querySelector('#main').textContent).toContain(
'A magical documentation site generator'
'A magical documentation site generator',
);
});
@ -78,7 +78,7 @@ describe('Creating a Docsify site (integration tests in Jest)', function () {
expect(typeof window.$docsify).toBe('object');
expect(window.$docsify).toHaveProperty('themeColor', 'red');
expect(document.querySelector('.app-name').textContent).toContain(
'Docsify Name'
'Docsify Name',
);
// Verify docsifyInitConfig.markdown content was rendered
@ -94,7 +94,7 @@ describe('Creating a Docsify site (integration tests in Jest)', function () {
// Verify docsifyInitConfig.scriptURLs were added to the DOM
for (const scriptURL of docsifyInitConfig.scriptURLs) {
const matchElm = document.querySelector(
`script[data-src$="${scriptURL}"]`
`script[data-src$="${scriptURL}"]`,
);
expect(matchElm).toBeTruthy();
}
@ -108,8 +108,8 @@ describe('Creating a Docsify site (integration tests in Jest)', function () {
[...document.querySelectorAll('script')].some(
elm =>
elm.textContent.replace(/\s+/g, '') ===
docsifyInitConfig.script.replace(/\s+/g, '')
)
docsifyInitConfig.script.replace(/\s+/g, ''),
),
).toBe(true);
// Verify docsifyInitConfig.script was executed
@ -118,7 +118,7 @@ describe('Creating a Docsify site (integration tests in Jest)', function () {
// Verify docsifyInitConfig.styleURLs were added to the DOM
for (const styleURL of docsifyInitConfig.styleURLs) {
const matchElm = document.querySelector(
`link[rel*="stylesheet"][href$="${styleURL}"]`
`link[rel*="stylesheet"][href$="${styleURL}"]`,
);
expect(matchElm).toBeTruthy();
}
@ -128,14 +128,14 @@ describe('Creating a Docsify site (integration tests in Jest)', function () {
[...document.querySelectorAll('style')].some(
elm =>
elm.textContent.replace(/\s+/g, '') ===
docsifyInitConfig.style.replace(/\s+/g, '')
)
docsifyInitConfig.style.replace(/\s+/g, ''),
),
).toBe(true);
// Verify docsify navigation and docsifyInitConfig.routes
document.querySelector('a[href="#/test"]').click();
expect(
await waitForFunction(() => /#\/test$/.test(window.location.href))
await waitForFunction(() => /#\/test$/.test(window.location.href)),
).toBeTruthy();
expect(await waitForText('#main', 'This is a custom route')).toBeTruthy();
});

View File

@ -16,14 +16,14 @@ describe('render', function () {
const output = window.marked('!> Important content');
expect(output).toMatchInlineSnapshot(
`"<p class="tip">Important content</p>"`
'"<p class="tip">Important content</p>"',
);
});
test('general tip', () => {
const output = window.marked('?> General tip');
expect(output).toMatchInlineSnapshot(`"<p class="warn">General tip</p>"`);
expect(output).toMatchInlineSnapshot('"<p class="warn">General tip</p>"');
});
});
@ -42,7 +42,7 @@ describe('render', function () {
`);
expect(output).toMatchInlineSnapshot(
`"<ul class="task-list"><li class="task-list-item"><label><input checked="" disabled="" type="checkbox"> Task 1</label></li><li class="task-list-item"><label><input disabled="" type="checkbox"> Task 2</label></li><li class="task-list-item"><label><input disabled="" type="checkbox"> Task 3</label></li></ul>"`
'"<ul class="task-list"><li class="task-list-item"><label><input checked="" disabled="" type="checkbox"> Task 1</label></li><li class="task-list-item"><label><input disabled="" type="checkbox"> Task 2</label></li><li class="task-list-item"><label><input disabled="" type="checkbox"> Task 3</label></li></ul>"',
);
});
@ -53,7 +53,7 @@ describe('render', function () {
`);
expect(output).toMatchInlineSnapshot(
`"<ol class="task-list"><li class="task-list-item"><label><input disabled="" type="checkbox"> Task 1</label></li><li class="task-list-item"><label><input checked="" disabled="" type="checkbox"> Task 2</label></li></ol>"`
'"<ol class="task-list"><li class="task-list-item"><label><input disabled="" type="checkbox"> Task 1</label></li><li class="task-list-item"><label><input checked="" disabled="" type="checkbox"> Task 2</label></li></ol>"',
);
});
@ -64,7 +64,7 @@ describe('render', function () {
`);
expect(output).toMatchInlineSnapshot(
`"<ul ><li><a href="#/link" >linktext</a></li><li>just text</li></ul>"`
'"<ul ><li><a href="#/link" >linktext</a></li><li>just text</li></ul>"',
);
});
@ -79,7 +79,7 @@ describe('render', function () {
`);
expect(output).toMatchInlineSnapshot(
`"<ol ><li>first</li><li>second</li></ol><p>text</p><ol start="3"><li>third</li></ol>"`
'"<ol ><li>first</li><li>second</li></ol><p>text</p><ol start="3"><li>third</li></ol>"',
);
});
@ -93,7 +93,7 @@ describe('render', function () {
`);
expect(output).toMatchInlineSnapshot(
`"<ul ><li>1</li><li>2<ul ><li>2 a</li><li>2 b</li></ul></li><li>3</li></ul>"`
'"<ul ><li>1</li><li>2<ul ><li>2 a</li><li>2 b</li></ul></li><li>3</li></ul>"',
);
});
});
@ -109,27 +109,27 @@ describe('render', function () {
const output = window.marked('![alt text](http://imageUrl)');
expect(output).toMatchInlineSnapshot(
`"<p><img src="http://imageUrl" data-origin="http://imageUrl" alt="alt text" /></p>"`
'"<p><img src="http://imageUrl" data-origin="http://imageUrl" alt="alt text" /></p>"',
);
});
test('class', async function () {
const output = window.marked(
"![alt text](http://imageUrl ':class=someCssClass')"
"![alt text](http://imageUrl ':class=someCssClass')",
);
expect(output).toMatchInlineSnapshot(
`"<p><img src="http://imageUrl" data-origin="http://imageUrl" alt="alt text" class="someCssClass" /></p>"`
'"<p><img src="http://imageUrl" data-origin="http://imageUrl" alt="alt text" class="someCssClass" /></p>"',
);
});
test('id', async function () {
const output = window.marked(
"![alt text](http://imageUrl ':id=someCssID')"
"![alt text](http://imageUrl ':id=someCssID')",
);
expect(output).toMatchInlineSnapshot(
`"<p><img src="http://imageUrl" data-origin="http://imageUrl" alt="alt text" id="someCssID" /></p>"`
'"<p><img src="http://imageUrl" data-origin="http://imageUrl" alt="alt text" id="someCssID" /></p>"',
);
});
@ -137,17 +137,17 @@ describe('render', function () {
const output = window.marked("![alt text](http://imageUrl ':no-zoom')");
expect(output).toMatchInlineSnapshot(
`"<p><img src="http://imageUrl" data-origin="http://imageUrl" alt="alt text" data-no-zoom /></p>"`
'"<p><img src="http://imageUrl" data-origin="http://imageUrl" alt="alt text" data-no-zoom /></p>"',
);
});
test('width and height', async function () {
const output = window.marked(
"![alt text](http://imageUrl ':size=WIDTHxHEIGHT')"
"![alt text](http://imageUrl ':size=WIDTHxHEIGHT')",
);
expect(output).toMatchInlineSnapshot(
`"<p><img src="http://imageUrl" data-origin="http://imageUrl" alt="alt text" width="WIDTH" height="HEIGHT" /></p>"`
'"<p><img src="http://imageUrl" data-origin="http://imageUrl" alt="alt text" width="WIDTH" height="HEIGHT" /></p>"',
);
});
@ -155,7 +155,7 @@ describe('render', function () {
const output = window.marked("![alt text](http://imageUrl ':size=50')");
expect(output).toMatchInlineSnapshot(
`"<p><img src="http://imageUrl" data-origin="http://imageUrl" alt="alt text" width="50" /></p>"`
'"<p><img src="http://imageUrl" data-origin="http://imageUrl" alt="alt text" width="50" /></p>"',
);
});
});
@ -171,7 +171,7 @@ describe('render', function () {
const output = window.marked('# h1 tag');
expect(output).toMatchInlineSnapshot(
`"<h1 id="h1-tag" tabindex="-1"><a href="#/?id=h1-tag" data-id="h1-tag" class="anchor"><span>h1 tag</span></a></h1>"`
'"<h1 id="h1-tag" tabindex="-1"><a href="#/?id=h1-tag" data-id="h1-tag" class="anchor"><span>h1 tag</span></a></h1>"',
);
});
@ -179,7 +179,7 @@ describe('render', function () {
const output = window.marked('## h2 tag');
expect(output).toMatchInlineSnapshot(
`"<h2 id="h2-tag" tabindex="-1"><a href="#/?id=h2-tag" data-id="h2-tag" class="anchor"><span>h2 tag</span></a></h2>"`
'"<h2 id="h2-tag" tabindex="-1"><a href="#/?id=h2-tag" data-id="h2-tag" class="anchor"><span>h2 tag</span></a></h2>"',
);
});
@ -187,7 +187,7 @@ describe('render', function () {
const output = window.marked('### h3 tag');
expect(output).toMatchInlineSnapshot(
`"<h3 id="h3-tag" tabindex="-1"><a href="#/?id=h3-tag" data-id="h3-tag" class="anchor"><span>h3 tag</span></a></h3>"`
'"<h3 id="h3-tag" tabindex="-1"><a href="#/?id=h3-tag" data-id="h3-tag" class="anchor"><span>h3 tag</span></a></h3>"',
);
});
@ -195,7 +195,7 @@ describe('render', function () {
const output = window.marked('#### h4 tag');
expect(output).toMatchInlineSnapshot(
`"<h4 id="h4-tag" tabindex="-1"><a href="#/?id=h4-tag" data-id="h4-tag" class="anchor"><span>h4 tag</span></a></h4>"`
'"<h4 id="h4-tag" tabindex="-1"><a href="#/?id=h4-tag" data-id="h4-tag" class="anchor"><span>h4 tag</span></a></h4>"',
);
});
@ -203,7 +203,7 @@ describe('render', function () {
const output = window.marked('##### h5 tag');
expect(output).toMatchInlineSnapshot(
`"<h5 id="h5-tag" tabindex="-1"><a href="#/?id=h5-tag" data-id="h5-tag" class="anchor"><span>h5 tag</span></a></h5>"`
'"<h5 id="h5-tag" tabindex="-1"><a href="#/?id=h5-tag" data-id="h5-tag" class="anchor"><span>h5 tag</span></a></h5>"',
);
});
@ -211,7 +211,7 @@ describe('render', function () {
const output = window.marked('###### h6 tag');
expect(output).toMatchInlineSnapshot(
`"<h6 id="h6-tag" tabindex="-1"><a href="#/?id=h6-tag" data-id="h6-tag" class="anchor"><span>h6 tag</span></a></h6>"`
'"<h6 id="h6-tag" tabindex="-1"><a href="#/?id=h6-tag" data-id="h6-tag" class="anchor"><span>h6 tag</span></a></h6>"',
);
});
});
@ -227,7 +227,7 @@ describe('render', function () {
const output = window.marked('[alt text](http://url)');
expect(output).toMatchInlineSnapshot(
`"<p><a href="http://url" target="_blank" rel="noopener">alt text</a></p>"`
'"<p><a href="http://url" target="_blank" rel="noopener">alt text</a></p>"',
);
});
@ -239,7 +239,7 @@ describe('render', function () {
const output = window.marked('[alt text](http://www.example.com)');
expect(output).toMatchInlineSnapshot(
`"<p><a href="http://www.example.com" target="_blank" rel="noopener">alt text</a></p>"`
'"<p><a href="http://www.example.com" target="_blank" rel="noopener">alt text</a></p>"',
);
});
@ -247,7 +247,7 @@ describe('render', function () {
const output = window.marked("[alt text](http://url ':disabled')");
expect(output).toMatchInlineSnapshot(
`"<p><a href="javascript:void(0)" target="_blank" rel="noopener" disabled>alt text</a></p>"`
'"<p><a href="javascript:void(0)" target="_blank" rel="noopener" disabled>alt text</a></p>"',
);
});
@ -255,7 +255,7 @@ describe('render', function () {
const output = window.marked("[alt text](http://url ':target=_self')");
expect(output).toMatchInlineSnapshot(
`"<p><a href="http://url" target="_self" >alt text</a></p>"`
'"<p><a href="http://url" target="_self" >alt text</a></p>"',
);
});
@ -263,17 +263,17 @@ describe('render', function () {
const output = window.marked("[alt text](/url ':target=_blank')");
expect(output).toMatchInlineSnapshot(
`"<p><a href="#/url" target="_blank">alt text</a></p>"`
'"<p><a href="#/url" target="_blank">alt text</a></p>"',
);
});
test('class', async function () {
const output = window.marked(
"[alt text](http://url ':class=someCssClass')"
"[alt text](http://url ':class=someCssClass')",
);
expect(output).toMatchInlineSnapshot(
`"<p><a href="http://url" target="_blank" rel="noopener" class="someCssClass">alt text</a></p>"`
'"<p><a href="http://url" target="_blank" rel="noopener" class="someCssClass">alt text</a></p>"',
);
});
@ -281,7 +281,7 @@ describe('render', function () {
const output = window.marked("[alt text](http://url ':id=someCssID')");
expect(output).toMatchInlineSnapshot(
`"<p><a href="http://url" target="_blank" rel="noopener" id="someCssID">alt text</a></p>"`
'"<p><a href="http://url" target="_blank" rel="noopener" id="someCssID">alt text</a></p>"',
);
});
});
@ -297,7 +297,7 @@ describe('render', function () {
expect(elm.textContent).toBe(expectText);
expect(elm.outerHTML).toMatchInlineSnapshot(
`"<button id="skip-to-content">Skip to main content</button>"`
'"<button id="skip-to-content">Skip to main content</button>"',
);
});

View File

@ -1,7 +0,0 @@
module.exports = {
env: {
'jest/globals': true,
},
extends: ['plugin:jest/recommended', 'plugin:jest/style'],
plugins: ['jest'],
};

View File

@ -27,7 +27,7 @@ describe('core/util', () => {
test('non external local url with more /', () => {
const result = isExternal(
`//////////////////${location.host}/docsify/demo.md`
`//////////////////${location.host}/docsify/demo.md`,
);
expect(result).toBeFalsy();
@ -54,7 +54,7 @@ describe('core/util', () => {
test('external url with more /', () => {
const result = isExternal(
'//////////////////example.github.io/docsify/demo.md'
'//////////////////example.github.io/docsify/demo.md',
);
expect(result).toBeTruthy();

View File

@ -25,7 +25,7 @@ describe('core/render/utils', () => {
test('getAndRemoveDocisfyIgnorConfig from <!-- {docsify-ignore} -->', () => {
const { content, ignoreAllSubs, ignoreSubHeading } =
getAndRemoveDocisfyIgnoreConfig(
'My Ignore Title<!-- {docsify-ignore} -->'
'My Ignore Title<!-- {docsify-ignore} -->',
);
expect(content).toBe('My Ignore Title');
expect(ignoreSubHeading).toBeTruthy();
@ -35,7 +35,7 @@ describe('core/render/utils', () => {
test('getAndRemoveDocisfyIgnorConfig from <!-- {docsify-ignore-all} -->', () => {
const { content, ignoreAllSubs, ignoreSubHeading } =
getAndRemoveDocisfyIgnoreConfig(
'My Ignore Title<!-- {docsify-ignore-all} -->'
'My Ignore Title<!-- {docsify-ignore-all} -->',
);
expect(content).toBe('My Ignore Title');
expect(ignoreAllSubs).toBeTruthy();
@ -64,18 +64,18 @@ describe('core/render/utils', () => {
describe('getAndRemoveConfig()', () => {
test('parse simple config', () => {
const result = getAndRemoveConfig(
`[filename](_media/example.md ':include')`
"[filename](_media/example.md ':include')",
);
expect(result).toMatchObject({
config: {},
str: `[filename](_media/example.md ':include')`,
str: "[filename](_media/example.md ':include')",
});
});
test('parse config with arguments', () => {
const result = getAndRemoveConfig(
`[filename](_media/example.md ':include :foo=bar :baz test')`
"[filename](_media/example.md ':include :foo=bar :baz test')",
);
expect(result).toMatchObject({
@ -83,18 +83,18 @@ describe('core/render/utils', () => {
foo: 'bar',
baz: true,
},
str: `[filename](_media/example.md ':include test')`,
str: "[filename](_media/example.md ':include test')",
});
});
test('parse config with double quotes', () => {
const result = getAndRemoveConfig(
`[filename](_media/example.md ":include")`
'[filename](_media/example.md ":include")',
);
expect(result).toMatchObject({
config: {},
str: `[filename](_media/example.md ":include")`,
str: '[filename](_media/example.md ":include")',
});
});
});
@ -122,7 +122,7 @@ describe('core/render/tpl', () => {
]);
expect(result).toBe(
/* html */ `<ul class="app-sub-sidebar"><li><a class="section-link" href="#/cover?id=basic-usage" title="Basic usage"><span style="color:red">Basic usage</span></a></li><li><a class="section-link" href="#/cover?id=custom-background" title="Custom background">Custom background</a></li><li><a class="section-link" href="#/cover?id=test" title="Test"><img src="/docs/_media/favicon.ico" data-origin="/_media/favicon.ico" alt="ico">Test</a></li></ul>`
/* html */ '<ul class="app-sub-sidebar"><li><a class="section-link" href="#/cover?id=basic-usage" title="Basic usage"><span style="color:red">Basic usage</span></a></li><li><a class="section-link" href="#/cover?id=custom-background" title="Custom background">Custom background</a></li><li><a class="section-link" href="#/cover?id=test" title="Test"><img src="/docs/_media/favicon.ico" data-origin="/_media/favicon.ico" alt="ico">Test</a></li></ul>',
);
});
});
@ -130,12 +130,12 @@ describe('core/render/tpl', () => {
describe('core/render/slugify', () => {
test('slugify()', () => {
const result = slugify(
`Bla bla bla <svg aria-label="broken" class="broken" viewPort="0 0 1 1"><circle cx="0.5" cy="0.5"/></svg>`
'Bla bla bla <svg aria-label="broken" class="broken" viewPort="0 0 1 1"><circle cx="0.5" cy="0.5"/></svg>',
);
const result2 = slugify(
`Another <span style="font-size: 1.2em" class="foo bar baz">broken <span class="aaa">example</span></span>`
'Another <span style="font-size: 1.2em" class="foo bar baz">broken <span class="aaa">example</span></span>',
);
expect(result).toBe(`bla-bla-bla-`);
expect(result2).toBe(`another-broken-example`);
expect(result).toBe('bla-bla-bla-');
expect(result2).toBe('another-broken-example');
});
});