refactor: Update test environments and lint configuration (#1736)

* Update test environments and lint configuration

Update Jest (unit + integration) and Playwright (e2e) test environments. Includes stability improvements for e2e tests using newer, more stable methods per the Playwright docs.

- Update Jest 26 => 27
- Update Jest-related libs (babel parser)
- Update Playwright 1.8 => Playwright Test 1.18
- Update GitHub CI (action versions, job parallelization, and matrices)
- Update ESLint 5 => 8
- Update ESLint-related libs (parser, prettier, Jest, Playwright)
- Fix test failures on M1-based Macs
- Fix e2e stability issues by replacing PW $ method calls
- Fix ESLint errors
- Fix incorrect CI flag on Jest runs (-ci => --ci)
- Refactor e2e test runner from Jest to Playwright Test
- Refactor e2e test files for Playwright Test
- Refactor fix-lint script name to lint:fix for consistency
- Refactor npm scripts order for readability
- Remove unnecessary configs and libs
- Remove example image snapshots
This commit is contained in:
John Hildenbiddle 2022-01-30 21:40:21 -06:00 committed by GitHub
parent b58941e88b
commit c49c39a4a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
72 changed files with 31362 additions and 7609 deletions

View File

@ -1,10 +1,9 @@
.git
packages/docsify-server-renderer/build.js
node_modules
build
server.js
lib
themes
build
docs/
**/*.md
build
docs
lib
node_modules
packages/docsify-server-renderer/build.js
server.js
themes

View File

@ -1,24 +1,28 @@
const prettierConfig = require('./.prettierrc');
module.exports = {
root: true,
parser: 'babel-eslint',
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,
node: true,
es6: true,
node: true,
},
plugins: ['prettier', 'import'],
extends: ['eslint:recommended', 'plugin:import/recommended'],
settings: {
'import/ignore': ['node_modules', '.json$'],
},
rules: {
'prettier/prettier': ['error'],
camelcase: ['warn'],
'no-useless-escape': ['warn'],
curly: ['error', 'all'],
'dot-notation': ['error'],
eqeqeq: ['error'],
@ -33,9 +37,16 @@ module.exports = {
'no-proto': ['error'],
'no-return-assign': ['error'],
'no-self-compare': ['error'],
'no-shadow': ['warn'],
'no-shadow-restricted-names': ['error'],
'no-shadow': [
'error',
{
allow: ['Events', 'Fetch', 'Lifecycle', 'Render', 'Router'],
},
],
'no-unused-vars': ['error', { args: 'none' }],
'no-useless-call': ['error'],
'no-useless-escape': ['warn'],
'no-var': ['error'],
'no-void': ['error'],
'no-with': ['error'],
@ -46,18 +57,21 @@ module.exports = {
// Import rules
// Search way how integrate with `lerna`
'import/no-unresolved': 'off',
'import/imports-first': ['error'],
'import/newline-after-import': ['error'],
'import/no-duplicates': ['error'],
'import/no-mutable-exports': ['error'],
'import/no-named-as-default': ['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',
Docsify: 'writable',
dom: 'writable',
},
};

View File

@ -1,30 +0,0 @@
name: Lint
on:
push:
branches:
- master
- develop
pull_request:
branches:
- master
- develop
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node-version: [12.x, 14.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Install
run: npm i --ignore-scripts
- name: Lint
run: npm run lint

View File

@ -2,41 +2,78 @@ name: Build & Test
on:
push:
branches:
- master
- develop
branches: [master, develop]
pull_request:
branches:
- master
- develop
branches: [master, develop]
jobs:
build:
lint:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: ['lts/*']
steps:
- uses: actions/checkout@v2
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci --ignore-scripts
- name: Build
run: npm run build
- name: Lint
run: npm run lint
test-jest:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
node-version: [12.x, 14.x]
node-version: ['lts/*']
os: ['macos-latest', 'ubuntu-latest', 'windows-latest']
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- name: bootstrap
run: npm run bootstrap
- name: unit tests
run: npm run test:unit -- -ci --runInBand
- name: integration tests
run: npm run test:integration -- -ci --runInBand
- uses: microsoft/playwright-github-action@v1
- name: e2e tests
run: npm run test:e2e -- --ci --runInBand
- name: Upload artifacts (diff output)
cache: 'npm'
- name: Install dependencies
run: npm ci --ignore-scripts
- name: Build
run: npm run build
- name: Unit Tests
run: npm run test:unit -- --ci --runInBand
- name: Integration Tests
run: npm run test:integration -- --ci --runInBand
test-playwright:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: ['lts/*']
steps:
- uses: actions/checkout@v2
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci --ignore-scripts
- name: Build
run: npm run build
- name: Install Playwright
run: npx playwright install --with-deps
- name: E2E Tests (Playwright)
run: npm run test:e2e
- name: Store artifacts
uses: actions/upload-artifact@v2
if: failure()
with:
name: ${{ matrix.os }}-${{ matrix.node-version }}-diff-output
path: ${{ github.workspace }}/test/**/__diff_output__/*
name: ${{ matrix.os }}-${{ matrix.node-version }}-artifacts
path: |
_playwright-results/
_playwright-report/

9
.gitignore vendored
View File

@ -1,10 +1,11 @@
*.log
.DS_Store
.idea
__diff_output__
lib/
*.log
_playwright-report
_playwright-results
lib
node_modules
themes/
themes
# exceptions
!.gitkeep

View File

@ -1,5 +1,5 @@
.eslintignore
.eslintrc
.github/
.github
.gitignore
.travis.yml

View File

@ -1,4 +1,4 @@
module.exports = {
arrowParens: 'avoid',
singleQuote: true,
trailingComma: 'es5',
};

View File

@ -1,3 +1,4 @@
{
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.defaultFormatter": "esbenp.prettier-vscode",
"cSpell.words": ["coverpage"]
}

View File

@ -2,60 +2,28 @@ const { TEST_HOST } = require('./test/config/server.js');
const sharedConfig = {
errorOnDeprecated: true,
globals: {
TEST_HOST,
},
globalSetup: './test/config/jest.setup.js',
globalTeardown: './test/config/jest.teardown.js',
resetModules: true,
restoreMocks: true,
setupFilesAfterEnv: ['<rootDir>/test/config/jest.setup-tests.js'],
testEnvironment: 'jsdom',
testURL: `${TEST_HOST}/_blank.html`,
};
module.exports = {
// Adding globals to config root for easier importing into .eslint.js, but
// as of Jest 26.4.2 these globals need to be added to each project config
// as well.
globals: sharedConfig.globals,
projects: [
// Unit Tests (Jest)
// Unit Tests
{
...sharedConfig,
displayName: 'unit',
setupFilesAfterEnv: ['<rootDir>/test/config/jest.setup-tests.js'],
...sharedConfig,
testMatch: ['<rootDir>/test/unit/*.test.js'],
testURL: `${TEST_HOST}/_blank.html`,
},
// Integration Tests (Jest)
// Integration Tests
{
...sharedConfig,
displayName: 'integration',
setupFilesAfterEnv: ['<rootDir>/test/config/jest.setup-tests.js'],
testMatch: ['<rootDir>/test/integration/*.test.js'],
testURL: `${TEST_HOST}/_blank.html`,
},
// E2E Tests (Jest + Playwright)
{
...sharedConfig,
displayName: 'e2e',
preset: 'jest-playwright-preset',
setupFilesAfterEnv: [
'<rootDir>/test/config/jest-playwright.setup-tests.js',
],
testEnvironmentOptions: {
'jest-playwright': {
// prettier-ignore
browsers: [
'chromium',
'firefox',
'webkit',
],
launchOptions: {
// headless: false,
// devtools: true,
},
},
},
testMatch: ['<rootDir>/test/e2e/*.test.js'],
testMatch: ['<rootDir>/test/integration/*.test.js'],
},
],
};

37724
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -21,39 +21,39 @@
],
"scripts": {
"bootstrap": "npm i && lerna bootstrap && npm run build:ssr",
"serve": "node server",
"serve:ssr": "cross-env SSR=1 node server",
"dev": "run-p serve watch:*",
"dev:ssr": "run-p serve:ssr watch:*",
"lint": "eslint .",
"fixlint": "eslint . --fix",
"test": "jest",
"build:test": "npm run build && npm test",
"test:e2e": "jest --selectProjects e2e",
"test:integration": "jest --selectProjects integration",
"test:unit": "jest --selectProjects unit",
"css": "node build/css",
"watch:css": "npm run css -- -o themes -w",
"watch:js": "node build/build.js",
"build:cover": "node build/cover.js",
"build:css:min": "mkdirp lib/themes && npm run css -- -o lib/themes && node build/mincss.js",
"build:css": "mkdirp themes && npm run css -- -o themes",
"build:js": "cross-env NODE_ENV=production node build/build.js",
"build:ssr": "node build/ssr.js",
"build:cover": "node build/cover.js",
"build:test": "npm run build && npm test",
"build": "rimraf lib themes && run-s build:js build:css build:css:min build:ssr build:cover",
"prepare": "npm run build",
"pub:next": "cross-env RELEASE_TAG=next sh build/release.sh",
"pub": "sh build/release.sh",
"postinstall": "opencollective-postinstall",
"css": "node build/css",
"dev:ssr": "run-p serve:ssr watch:*",
"dev": "run-p serve watch:*",
"docker:build:test": "npm run docker:cli -- build:test",
"docker:build": "docker build -f Dockerfile -t docsify-test:local .",
"docker:clean": "docker rmi docsify-test:local",
"docker:cli": "docker run --rm -it --ipc=host --mount type=bind,source=$(pwd)/test,target=/app/test docsify-test:local",
"docker:rebuild": "npm run docker:clean && npm run docker:build",
"docker:test": "npm run docker:cli -- test",
"docker:build:test": "npm run docker:cli -- build:test",
"docker:test:e2e": "npm run docker:cli -- test:e2e",
"docker:test:integration": "npm run docker:cli -- test:integration",
"docker:test:unit": "npm run docker:cli -- test:unit",
"docker:cli": "docker run --rm -it --ipc=host --mount type=bind,source=$(pwd)/test,target=/app/test docsify-test:local"
"docker:test": "npm run docker:cli -- test",
"lint:fix": "eslint . --fix",
"lint": "eslint .",
"postinstall": "opencollective-postinstall",
"prepare": "npm run build",
"pub:next": "cross-env RELEASE_TAG=next sh build/release.sh",
"pub": "sh build/release.sh",
"serve:ssr": "cross-env SSR=1 node server",
"serve": "node server",
"test:e2e": "playwright test",
"test:integration": "jest --selectProjects integration",
"test:unit": "jest --selectProjects unit",
"test": "jest && run-s test:e2e",
"watch:css": "npm run css -- -o themes -w",
"watch:js": "node build/build.js"
},
"husky": {
"hooks": {
@ -75,11 +75,12 @@
},
"devDependencies": {
"@babel/core": "^7.11.6",
"@babel/eslint-parser": "^7.16.5",
"@babel/preset-env": "^7.11.5",
"@playwright/test": "^1.18.1",
"autoprefixer-stylus": "^1.0.0",
"axios": "^0.21.1",
"babel-eslint": "^10.0.3",
"babel-jest": "^26.3.0",
"babel-jest": "^27.4.6",
"browser-sync": "^2.26.12",
"chokidar": "^3.4.2",
"common-tags": "^1.8.0",
@ -87,22 +88,20 @@
"copy-dir": "^1.2.0",
"cross-env": "^6.0.3",
"cssnano": "^4.1.10",
"eslint": "^5.16.0",
"eslint": "^8.7.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-jest": "^24.0.2",
"eslint-plugin-jest-playwright": "^0.2.1",
"eslint-plugin-prettier": "^3.1.2",
"eslint-plugin-jest": "^26.0.0",
"eslint-plugin-playwright": "^0.8.0",
"eslint-plugin-prettier": "^4.0.0",
"husky": "^3.1.0",
"jest": "^26.4.2",
"jest-image-snapshot": "^4.2.0",
"jest-playwright-preset": "^1.4.1",
"jest": "^27.4.7",
"lerna": "^3.22.1",
"lint-staged": "^10.4.0",
"live-server": "^1.2.1",
"mkdirp": "^0.5.1",
"npm-run-all": "^4.1.5",
"playwright": "^1.8.0",
"prettier": "^1.19.1",
"prettier": "^2.5.1",
"rimraf": "^3.0.0",
"rollup": "^1.23.1",
"rollup-plugin-async": "^1.2.0",

64
playwright.config.js Normal file
View File

@ -0,0 +1,64 @@
const { devices } = require('@playwright/test');
/**
* @see https://playwright.dev/docs/test-configuration
* @type {import('@playwright/test').PlaywrightTestConfig}
*/
const config = {
// Setup / Teardown
globalSetup: './test/config/playwright.setup.js',
globalTeardown: './test/config/playwright.teardown.js',
// Test Execution
expect: {
timeout: 5000,
},
retries: process.env.CI ? 2 : 0, // Retry on CI only
testDir: './test/e2e',
timeout: 30 * 1000,
workers: process.env.CI ? 1 : undefined, // No parallel tests on CI
forbidOnly: !!process.env.CI, // Fail on CI if test.only in source
// Output
outputDir: './_playwright-results/', // screenshots, videos, traces, etc.
reporter: [
[
'html',
{
open: 'never',
outputFolder: '_playwright-report',
},
],
],
snapshotDir: './test/e2e/__snapshots__',
// Config - Shared
// See https://playwright.dev/docs/api/class-testoptions
use: {
actionTimeout: 0,
baseURL: `${process.env.TEST_HOST}`, // Allow relative page.goto() (e.g. `await page.goto('/')`).
trace: 'on-first-retry',
},
// Projects
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] }
// },
],
};
module.exports = config;

View File

@ -3,7 +3,7 @@ import { merge, hyphenate, isPrimitive, hasOwn } from './util/core';
const currentScript = document.currentScript;
/** @param {import('./Docsify').Docsify} vm */
export default function(vm) {
export default function (vm) {
const config = merge(
{
el: '#app',

View File

@ -13,7 +13,7 @@ const cache = {};
*/
export function get(url, hasBar = false, headers = {}) {
const xhr = new XMLHttpRequest();
const on = function() {
const on = function () {
xhr.addEventListener.apply(xhr, arguments);
};
@ -33,7 +33,7 @@ export function get(url, hasBar = false, headers = {}) {
xhr.send();
return {
then: function(success, error = noop) {
then: function (success, error = noop) {
if (hasBar) {
const id = setInterval(
_ =>

View File

@ -152,11 +152,9 @@ export function Fetch(Base) {
if (path) {
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));
get(path + stringifyQuery(query, ['id']), false, requestHeaders).then(
text => this._renderCover(text, coverOnly)
);
} else {
this._renderCover(null, coverOnly);
}

View File

@ -10,7 +10,7 @@ import { get } from './fetch/ajax';
// major release. We'll tell people to get everything from the DOCSIFY global
// when using the global build, but we'll highly recommend for them to import
// from the ESM build (f.e. lib/docsify.esm.js and lib/docsify.min.esm.js).
export default function() {
export default function () {
window.Docsify = {
util,
dom,

View File

@ -30,7 +30,7 @@ export function Lifecycle(Base) {
callHook(hookName, data, next = noop) {
const queue = this._hooks[hookName];
const step = function(index) {
const step = function (index) {
const hookFn = queue[index];
if (index >= queue.length) {

View File

@ -28,8 +28,9 @@ const compileMedia = {
},
iframe(url, title) {
return {
html: `<iframe src="${url}" ${title ||
'width=100% height=400'}></iframe>`,
html: `<iframe src="${url}" ${
title || 'width=100% height=400'
}></iframe>`,
};
},
video(url, title) {
@ -204,7 +205,7 @@ export class Compiler {
* @param {Number} level Type of heading (h<level> tag)
* @returns {String} Heading element
*/
origin.heading = renderer.heading = function(text, level) {
origin.heading = renderer.heading = function (text, level) {
let { str, config } = getAndRemoveConfig(text);
const nextToc = { level, title: removeAtag(str) };

View File

@ -3,7 +3,7 @@ import Prism from 'prismjs';
import 'prismjs/components/prism-markup-templating';
export const highlightCodeCompiler = ({ renderer }) =>
(renderer.code = function(code, lang = 'markup') {
(renderer.code = function (code, lang = 'markup') {
const langOrMarkup = Prism.languages[lang] || Prism.languages.markup;
const text = Prism.highlight(
code.replace(/@DOCSIFY_QM@/g, '`'),

View File

@ -15,7 +15,7 @@ function walkFetchEmbed({ embedTokens, compile, fetch }, cb) {
while ((token = embedTokens[step++])) {
// eslint-disable-next-line no-shadow
const next = (function(token) {
const next = (function (token) {
return text => {
let embedToken;
if (text) {

View File

@ -15,6 +15,6 @@ export function emojify(text) {
.replace(/<(pre|template|code)[^>]*?>[\s\S]+?<\/(pre|template|code)>/g, m =>
m.replace(/:/g, '__colon__')
)
.replace(/:([a-z0-9_\-\+]+?):/g, (inBrowser && window.emojify) || replace)
.replace(/:([a-z0-9_\-+]+?):/g, (inBrowser && window.emojify) || replace)
.replace(/__colon__/g, ':');
}

View File

@ -174,7 +174,7 @@ function renderMain(html) {
// This provides a global store for all Vue instances that receive
// vueGlobalOptions as their configuration.
if (vueGlobalData) {
vueConfig.data = function() {
vueConfig.data = function () {
return vueGlobalData;
};
}

View File

@ -17,7 +17,7 @@ function init() {
/**
* Render progress bar
*/
export default function({ loaded, total, step }) {
export default function ({ loaded, total, step }) {
let num;
!barEl && init();

View File

@ -32,6 +32,6 @@ export function slugify(str) {
return slug;
}
slugify.clear = function() {
slugify.clear = function () {
cache = {};
};

View File

@ -13,7 +13,7 @@ export function parseQuery(query) {
}
// Simple parse
query.split('&').forEach(function(param) {
query.split('&').forEach(function (param) {
const parts = param.replace(/\+/g, ' ').split('=');
res[parts[0]] = parts[1] && decode(parts[1]);

View File

@ -6,7 +6,7 @@
export function cached(fn) {
const cache = Object.create(null);
return function(str) {
return function (str) {
const key = isPrimitive(str) ? str : JSON.stringify(str);
const hit = cache[key];
return hit || (cache[key] = fn(str));
@ -29,7 +29,7 @@ export const hasOwn = Object.prototype.hasOwnProperty;
*/
export const merge =
Object.assign ||
function(to) {
function (to) {
for (let i = 1; i < arguments.length; i++) {
const from = Object(arguments[i]);

View File

@ -7,7 +7,7 @@ export const isMobile = inBrowser && document.body.clientWidth <= 600;
*/
export const supportsPushState =
inBrowser &&
(function() {
(function () {
// Borrowed wholesale from https://github.com/defunkt/jquery-pjax
return (
window.history &&

View File

@ -8,7 +8,7 @@ function replaceVar(block, color) {
);
}
export default function(color) {
export default function (color) {
// Variable support
if (window.CSS && window.CSS.supports && window.CSS.supports('(--v:red)')) {
return;

View File

@ -39,7 +39,7 @@ function install(hook, vm) {
if (typeof window.DISQUS !== 'undefined') {
window.DISQUS.reset({
reload: true,
config: function() {
config: function () {
this.page.url = location.origin + '/-' + vm.route.path;
this.page.identifier = vm.route.path;
this.page.title = document.title;

View File

@ -1,7 +1,7 @@
/* eslint-disable camelcase */
const AllGithubEmoji = {
'100': 'unicode/1f4af',
'1234': 'unicode/1f522',
100: 'unicode/1f4af',
1234: 'unicode/1f522',
'+1': 'unicode/1f44d',
'-1': 'unicode/1f44e',
'1st_place_medal': 'unicode/1f947',
@ -1804,8 +1804,8 @@ const AllGithubEmoji = {
// Emoji from GitHub API
// https://api.github.com/emojis
window.emojify = function(match, $1) {
return AllGithubEmoji.hasOwnProperty($1) === false
window.emojify = function (match, $1) {
return Object.prototype.hasOwnProperty.call(AllGithubEmoji, $1) === false
? match
: '<img class="emoji" src="https://github.githubassets.com/images/icons/emoji/' +
AllGithubEmoji[$1] +

View File

@ -18,7 +18,7 @@ function handleExternalScript() {
}
}
const install = function(hook) {
const install = function (hook) {
hook.doneEach(handleExternalScript);
};

View File

@ -1,10 +1,10 @@
import parser from './parser';
const install = function(hook, vm) {
const install = function (hook, vm) {
// Used to remove front matter from embedded pages if installed.
vm.config.frontMatter = {};
vm.config.frontMatter.installed = true;
vm.config.frontMatter.parseMarkdown = function(content) {
vm.config.frontMatter.parseMarkdown = function (content) {
const { body } = parser(content);
return body;
};

View File

@ -11,7 +11,7 @@ function init(id) {
appendScript();
window.ga =
window.ga ||
function() {
function () {
(window.ga.q = window.ga.q || []).push(arguments);
};
@ -28,7 +28,7 @@ function collect() {
window.ga('send', 'pageview');
}
const install = function(hook) {
const install = function (hook) {
if (!$docsify.ga) {
console.error('[Docsify] ga is required.');
return;

View File

@ -9,7 +9,7 @@ function init(options) {
window._paq = window._paq || [];
window._paq.push(['trackPageView']);
window._paq.push(['enableLinkTracking']);
setTimeout(function() {
setTimeout(function () {
appendScript(options);
window._paq.push(['setTrackerUrl', options.host + '/matomo.php']);
window._paq.push(['setSiteId', String(options.id)]);
@ -26,7 +26,7 @@ function collect() {
window._paq.push(['trackPageView']);
}
const install = function(hook) {
const install = function (hook) {
if (!$docsify.matomo) {
// eslint-disable-next-line no-console
console.error('[Docsify] matomo is required.');

View File

@ -13,7 +13,7 @@ const CONFIG = {
pathNamespaces: undefined,
};
const install = function(hook, vm) {
const install = function (hook, vm) {
const { util } = Docsify;
const opts = vm.config.search || CONFIG;

View File

@ -58,7 +58,7 @@ function getTableData(token) {
if (!token.text && token.type === 'table') {
token.cells.unshift(token.header);
token.text = token.cells
.map(function(rows) {
.map(function (rows) {
return rows.join(' | ');
})
.join(' |\n ');
@ -85,7 +85,7 @@ export function genIndex(path, content = '', router, depth) {
let slug;
let title = '';
tokens.forEach(function(token, tokenIndex) {
tokens.forEach(function (token, tokenIndex) {
if (token.type === 'heading' && token.depth <= depth) {
const { str, config } = getAndRemoveConfig(token.text);

View File

@ -14,7 +14,7 @@
## Global Variables
- `TEST_HOST`: Test server ip:port
- `process.env.TEST_HOST`: Test server ip:port
## CLI commands

View File

@ -1,54 +0,0 @@
/* global browserName page */
const { configureToMatchImageSnapshot } = require('jest-image-snapshot');
// Lifecycle Hooks
// -----------------------------------------------------------------------------
beforeAll(async () => {
// Storing separate image comparison configurations for easy switching while
// evaluating results. Once more e2e tests are in place, we'll settle on a
// configuration, allowing us to safely remove the multi-config object below.
const config = {
// Pixel-based image comparisons
// https://github.com/mapbox/pixelmatch#pixelmatchimg1-img2-output-width-height-options
pixelCompare: {
customDiffConfig: {
threshold: 0.3,
},
failureThreshold: 0.04,
},
// Structural Similarity Index Measure (SSIM) comparisons
// https://github.com/obartra/ssim
ssimCompare: {
comparisonMethod: 'ssim',
failureThreshold: 0.15,
},
};
const toMatchImageSnapshot = configureToMatchImageSnapshot({
allowSizeMismatch: true, // Windows CI fix
customSnapshotIdentifier(data) {
return `${data.defaultIdentifier}-${browserName}`;
},
diffDirection: 'vertical',
failureThresholdType: 'percent',
noColors: true,
runInProcess: true, // macOS CI fix
// pixel- or ssim-based configuration
...config.pixelCompare,
});
expect.extend({ toMatchImageSnapshot });
});
beforeEach(async () => {
await global.jestPlaywright.resetContext();
// Goto URL ()
// https://playwright.dev/#path=docs%2Fapi.md&q=pagegotourl-options
// NOTE: Tests typically begin by navigating to a page for testing. When
// this doesn't happen, Playwright operates on the "about:blank" page which
// will cause operations that require the window location to be a valid URL
// to fail (e.g. AJAX requests). To avoid these issues, this hook ensures
// that each tests begins by a blank HTML page.
await page.goto(`${TEST_HOST}/_blank.html`);
});

View File

@ -1,3 +1,5 @@
/* global afterEach, beforeAll, beforeEach */
import mock from 'xhr-mock';
const sideEffects = {

View File

@ -0,0 +1,5 @@
const server = require('./server.js');
module.exports = async config => {
await server.startAsync();
};

View File

@ -0,0 +1,5 @@
const server = require('./server.js');
module.exports = async config => {
server.stop();
};

View File

@ -2,10 +2,9 @@ const browserSync = require('browser-sync').create();
const path = require('path');
const hasStartArg = process.argv.includes('--start');
const serverConfig = {
host: '127.0.0.1',
port: 3001,
hostname: '127.0.0.1',
port: hasStartArg ? 3002 : 3001,
};
function startServer(options = {}, cb = Function.prototype) {
@ -14,7 +13,7 @@ function startServer(options = {}, cb = Function.prototype) {
middleware: [
{
route: '/_blank.html',
handle: function(req, res, next) {
handle: function (req, res, next) {
res.setHeader('Content-Type', 'text/html');
res.end('');
next();
@ -26,7 +25,8 @@ function startServer(options = {}, cb = Function.prototype) {
rewriteRules: [
// Replace docsify-related CDN URLs with local paths
{
match: /(https?:)?\/\/cdn\.jsdelivr\.net\/npm\/docsify(@\d?\.?\d?\.?\d)?\/lib\//g,
match:
/(https?:)?\/\/cdn\.jsdelivr\.net\/npm\/docsify(@\d?\.?\d?\.?\d)?\/lib\//g,
replace: '/lib/',
},
],
@ -42,7 +42,7 @@ function startServer(options = {}, cb = Function.prototype) {
snippetOptions: {
rule: {
match: /<\/body>/i,
fn: function(snippet, match) {
fn: function (snippet, match) {
// Override changelog alias to load local changelog (see routes)
const injectJS = `
<script>
@ -64,6 +64,10 @@ function startServer(options = {}, cb = Function.prototype) {
console.log('\n');
// Set TEST_HOST environment variable
process.env.TEST_HOST = `http://${serverConfig.hostname}:${serverConfig.port}`;
// Start server
browserSync.init(
// Config
{
@ -93,7 +97,7 @@ function stopServer() {
if (hasStartArg) {
startServer({
open: true,
port: serverConfig.port + 1,
port: serverConfig.port,
directory: true,
startPath: '/docs',
});
@ -107,5 +111,5 @@ module.exports = {
start: startServer,
startAsync: startServerAsync,
stop: stopServer,
TEST_HOST: `http://${serverConfig.host}:${serverConfig.port}`,
TEST_HOST: `http://${serverConfig.hostname}:${serverConfig.port}`,
};

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -1,48 +1,37 @@
// Modules, constants, and variables
// -----------------------------------------------------------------------------
const docsifyInit = require('../helpers/docsify-init');
const { test, expect } = require('./fixtures/docsify-init-fixture');
// Suite
// -----------------------------------------------------------------------------
describe(`Example Tests`, function() {
test.describe('Example Tests', () => {
// Tests
// ---------------------------------------------------------------------------
test('dom manipulation', async () => {
test('dom manipulation', async ({ page }) => {
const testText = 'This is a test';
const testHTML = `<h1>Test</h1><p>${testText}</p>`;
// Inject HTML
// https://playwright.dev/#path=docs%2Fapi.md&q=pagesetcontenthtml-options
await page.setContent(testHTML);
// Add class to <body> element and test
// https://playwright.dev/#path=docs%2Fapi.md&q=pageevalselector-pagefunction-arg
await page.$eval('body', elm => elm.classList.add('foo'));
expect(await page.getAttribute('body', 'class')).toEqual('foo');
// Get reference to page element
const bodyElm = page.locator('body');
const pElm = page.locator('body > p');
// Test using helper methods from expect-playright (via jest-playwright)
// https://github.com/playwright-community/expect-playwright
// https://playwright.tech/blog/using-jest-with-playwright
await expect(page).toHaveText('body', 'Test');
await expect(page).toHaveSelector('p');
await expect(page).toEqualText('p', testText);
await expect(page).not.toHaveSelector('table', { timeout: 1 });
// Add class to element and test
await bodyElm.evaluate(elm => elm.classList.add('foo'));
// Test using standard jest + playwrite methods
// https://playwright.dev/#path=docs%2Fapi.md&q=pagetextcontentselector-options
expect(await page.textContent('body')).toMatch(/Test/);
await page.waitForSelector('p');
expect(await page.textContent('p')).toEqual(testText);
await page.waitForSelector('table', { state: 'detached' });
// Debug mode
// https://github.com/playwright-community/jest-playwright#put-in-debug-mode
// await jestPlaywright.debug();
// Tests
await expect(bodyElm).toHaveClass('foo');
await expect(bodyElm).toContainText('Test');
await expect(pElm).toHaveCount(1);
await expect(pElm).toHaveText(testText);
await expect(pElm).not.toHaveText('NOPE');
});
test('javascript in browser context', async () => {
test('javascript in browser context', async ({ page }) => {
// Get native DOM values
// https://playwright.dev/#path=docs%2Fapi.md&q=pageevaluatepagefunction-arg
const clientDimensions = await page.evaluate(() => {
return {
width: document.documentElement.clientWidth,
@ -56,7 +45,6 @@ describe(`Example Tests`, function() {
expect(typeof clientDimensions.height).toBe('number');
// Get result of script executed in browser context
// https://playwright.dev/#path=docs%2Fapi.md&q=pageevaluatepagefunction-arg
const scriptResult = await page.evaluate(
numbers => {
const result = numbers.reduce(
@ -71,7 +59,6 @@ describe(`Example Tests`, function() {
expect(scriptResult).toBe(6);
// Get result of local function executed in browser context
// https://playwright.dev/#path=docs%2Fapi.md&q=pageevaluatepagefunction-arg
function add(...addends) {
return addends.reduce(
(accumulator, currentValue) => accumulator + currentValue
@ -79,7 +66,7 @@ describe(`Example Tests`, function() {
}
const functionResult = await page.evaluate(`
${add.toString()}
const add = ${add.toString()};
const result = add(1, 2, 3);
@ -89,13 +76,11 @@ describe(`Example Tests`, function() {
expect(functionResult).toBe(6);
});
test('manual docsify site using playwright methods', async () => {
// Goto URL
// https://playwright.dev/#path=docs%2Fapi.md&q=pagegotourl-options
await page.goto(`${TEST_HOST}/_blank.html`);
test('manual docsify site using playwright methods', async ({ page }) => {
// Add docsify target element
await page.setContent('<div id="app"></div>');
// Set docsify configuration
// https://playwright.dev/#path=docs%2Fapi.md&q=pageevaluatepagefunction-arg
await page.evaluate(() => {
window.$docsify = {
el: '#app',
@ -104,58 +89,42 @@ describe(`Example Tests`, function() {
};
});
// Add docsify target element
// https://playwright.dev/#path=docs%2Fapi.md&q=pageevalselector-pagefunction-arg
await page.$eval('body', elm => {
elm.innerHTML = '<div id="app"></div>';
});
// Inject docsify theme (vue.css)
// https://playwright.dev/#path=docs%2Fapi.md&q=pageaddstyletagoptions
await page.addStyleTag({ url: '/lib/themes/vue.css' });
// Inject docsify.js
// https://playwright.dev/#path=docs%2Fapi.md&q=pageaddscripttagoptions
await page.addScriptTag({ url: '/lib/docsify.js' });
// Wait for docsify to initialize
// https://playwright.dev/#path=docs%2Fapi.md&q=pagewaitforselectorselector-options
await page.waitForSelector('#main');
// Create handle for JavaScript object in browser
// https://playwright.dev/#path=docs%2Fapi.md&q=pageevaluatepagefunction-arg
const $docsify = await page.evaluate(() => window.$docsify);
// const $docsify = await page.evaluateHandle(() => window.$docsify);
// Test object property and value
expect($docsify).toHaveProperty('themeColor', 'red');
});
test('Docsify /docs/ site using docsifyInit()', async () => {
test('Docsify /docs/ site using docsifyInit()', async ({ page }) => {
// Load custom docsify
// (See ./helpers/docsifyInit.js for details)
await docsifyInit({
config: {
basePath: '/docs/',
},
// _debug: true,
// _logHTML: true,
});
// Create handle for JavaScript object in browser
// https://playwright.dev/#path=docs%2Fapi.md&q=pageevaluatepagefunction-arg
const $docsify = await page.evaluate(() => window.$docsify);
// Verify config options
expect(typeof $docsify).toEqual('object');
// Verify docsifyInitConfig.markdown content was rendered
await expect(page).toHaveText(
'#main',
const mainElm = page.locator('#main');
await expect(mainElm).toHaveCount(1);
await expect(mainElm).toContainText(
'A magical documentation site generator'
);
});
test('custom docsify site using docsifyInit()', async () => {
test('custom docsify site using docsifyInit()', async ({ page }) => {
const docsifyInitConfig = {
config: {
name: 'Docsify Name',
@ -210,7 +179,6 @@ describe(`Example Tests`, function() {
await docsifyInit({
...docsifyInitConfig,
// _debug: true,
// _logHTML: true,
});
@ -219,24 +187,22 @@ describe(`Example Tests`, function() {
// Verify config options
expect(typeof $docsify).toEqual('object');
expect($docsify).toHaveProperty('themeColor', 'red');
await expect(page).toHaveText('.app-name', 'Docsify Name');
await expect(page.locator('.app-name')).toHaveText('Docsify Name');
// Verify docsifyInitConfig.markdown content was rendered
await expect(page).toHaveText('section.cover', 'Docsify Test'); // Coverpage
await expect(page).toHaveText('nav.app-nav', 'docsify.js.org'); // Navbar
await expect(page).toHaveText('aside.sidebar', 'Test Page'); // Sidebar
await expect(page).toHaveText('#main', 'This is the homepage'); // Homepage
await expect(page.locator('section.cover h1')).toHaveText('Docsify Test'); // Coverpage
await expect(page.locator('nav.app-nav')).toHaveText('docsify.js.org'); // Navbar
await expect(page.locator('aside.sidebar')).toContainText('Test Page'); // Sidebar
await expect(page.locator('#main')).toContainText('This is the homepage'); // Homepage
// Verify docsifyInitConfig.scriptURLs were added to the DOM
for (const scriptURL of docsifyInitConfig.scriptURLs) {
await expect(page).toHaveSelector(`script[src$="${scriptURL}"]`, {
state: 'attached',
});
await expect(page.locator(`script[src$="${scriptURL}"]`)).toHaveCount(1);
}
// Verify docsifyInitConfig.scriptURLs were executed
await expect(page).toHaveSelector('body[data-test-scripturls]');
await expect(page).toHaveSelector('.search input[type="search"]');
await expect(page.locator('body[data-test-scripturls]')).toHaveCount(1);
await expect(page.locator('.search input[type="search"]')).toHaveCount(1);
// Verify docsifyInitConfig.script was added to the DOM
expect(
@ -248,16 +214,13 @@ describe(`Example Tests`, function() {
).toBe(true);
// Verify docsifyInitConfig.script was executed
await expect(page).toHaveSelector('body[data-test-script]');
await expect(page.locator('body[data-test-script]')).toHaveCount(1);
// Verify docsifyInitConfig.styleURLs were added to the DOM
for (const styleURL of docsifyInitConfig.styleURLs) {
await expect(page).toHaveSelector(
`link[rel*="stylesheet"][href$="${styleURL}"]`,
{
state: 'attached',
}
);
await expect(
page.locator(`link[rel*="stylesheet"][href$="${styleURL}"]`)
).toHaveCount(1);
}
// Verify docsifyInitConfig.style was added to the DOM
@ -272,73 +235,68 @@ describe(`Example Tests`, function() {
// Verify docsify navigation and docsifyInitConfig.routes
await page.click('a[href="#/test"]');
expect(page.url()).toMatch(/\/test$/);
await expect(page).toHaveText('#main', 'This is a custom route');
await expect(page.locator('#main')).toContainText('This is a custom route');
});
test('image snapshots', async () => {
await docsifyInit({
config: {
name: 'Docsify Test',
},
markdown: {
homepage: `
# The Cosmos Awaits
// test.fixme('image snapshots', async ({ page }) => {
// await docsifyInit({
// config: {
// name: 'Docsify Test',
// },
// markdown: {
// homepage: `
// # The Cosmos Awaits
[Carl Sagan](https://en.wikipedia.org/wiki/Carl_Sagan)
// [Carl Sagan](https://en.wikipedia.org/wiki/Carl_Sagan)
Cosmic ocean take root and flourish decipherment hundreds of thousands
dream of the mind's eye courage of our questions. At the edge of forever
network of wormholes ship of the imagination two ghostly white figures
in coveralls and helmets are softly dancing are creatures of the cosmos
the only home we've ever known? How far away emerged into consciousness
bits of moving fluff gathered by gravity with pretty stories for which
there's little good evidence vanquish the impossible.
// Cosmic ocean take root and flourish decipherment hundreds of thousands
// dream of the mind's eye courage of our questions. At the edge of forever
// network of wormholes ship of the imagination two ghostly white figures
// in coveralls and helmets are softly dancing are creatures of the cosmos
// the only home we've ever known? How far away emerged into consciousness
// bits of moving fluff gathered by gravity with pretty stories for which
// there's little good evidence vanquish the impossible.
The ash of stellar alchemy permanence of the stars shores of the cosmic
ocean billions upon billions Drake Equation finite but unbounded.
Hundreds of thousands cosmic ocean hearts of the stars Hypatia invent
the universe hearts of the stars? Realm of the galaxies muse about dream
of the mind's eye hundreds of thousands the only home we've ever known
how far away. Extraordinary claims require extraordinary evidence
citizens of distant epochs invent the universe as a patch of light the
carbon in our apple pies gathered by gravity.
// The ash of stellar alchemy permanence of the stars shores of the cosmic
// ocean billions upon billions Drake Equation finite but unbounded.
// Hundreds of thousands cosmic ocean hearts of the stars Hypatia invent
// the universe hearts of the stars? Realm of the galaxies muse about dream
// of the mind's eye hundreds of thousands the only home we've ever known
// how far away. Extraordinary claims require extraordinary evidence
// citizens of distant epochs invent the universe as a patch of light the
// carbon in our apple pies gathered by gravity.
Billions upon billions gathered by gravity white dwarf intelligent
beings vanquish the impossible descended from astronomers. A still more
glorious dawn awaits cosmic ocean star stuff harvesting star light the
sky calls to us kindling the energy hidden in matter rich in heavy
atoms. A mote of dust suspended in a sunbeam across the centuries the
only home we've ever known bits of moving fluff a very small stage in a
vast cosmic arena courage of our questions.
// Billions upon billions gathered by gravity white dwarf intelligent
// beings vanquish the impossible descended from astronomers. A still more
// glorious dawn awaits cosmic ocean star stuff harvesting star light the
// sky calls to us kindling the energy hidden in matter rich in heavy
// atoms. A mote of dust suspended in a sunbeam across the centuries the
// only home we've ever known bits of moving fluff a very small stage in a
// vast cosmic arena courage of our questions.
Euclid the only home we've ever known realm of the galaxies trillion
radio telescope Apollonius of Perga. The carbon in our apple pies invent
the universe muse about stirred by starlight great turbulent clouds
emerged into consciousness? Invent the universe vastness is bearable
only through love a still more glorious dawn awaits descended from
astronomers as a patch of light the sky calls to us. Great turbulent
clouds citizens of distant epochs invent the universe two ghostly white
figures in coveralls and helmets are softly dancing courage of our
questions rich in heavy atoms and billions upon billions upon billions
upon billions upon billions upon billions upon billions.
`,
},
styleURLs: [`/lib/themes/vue.css`],
// _debug: true,
// _logHTML: true,
});
// Euclid the only home we've ever known realm of the galaxies trillion
// radio telescope Apollonius of Perga. The carbon in our apple pies invent
// the universe muse about stirred by starlight great turbulent clouds
// emerged into consciousness? Invent the universe vastness is bearable
// only through love a still more glorious dawn awaits descended from
// astronomers as a patch of light the sky calls to us. Great turbulent
// clouds citizens of distant epochs invent the universe two ghostly white
// figures in coveralls and helmets are softly dancing courage of our
// questions rich in heavy atoms and billions upon billions upon billions
// upon billions upon billions upon billions upon billions.
// `,
// },
// styleURLs: [`/lib/themes/vue.css`],
// // _logHTML: true,
// });
// Viewport screenshot
const screenshot1 = await page.screenshot();
expect(screenshot1).toMatchImageSnapshot();
// // Viewport screenshot
// const viewportShot = await page.screenshot();
// expect(viewportShot).toMatchSnapshot('viewport.png');
// Full page screenshot
const screenshot2 = await page.screenshot({ fullPage: true });
expect(screenshot2).toMatchImageSnapshot();
// Element screenshot
const elmHandle = await page.$('h1');
const screenshot3 = await elmHandle.screenshot();
expect(screenshot3).toMatchImageSnapshot();
});
// // Element screenshot
// const elmHandle = await page.locator('h1').first();
// const elmShot = await elmHandle.screenshot();
// expect(elmShot).toMatchSnapshot('element.png');
// });
});

View File

@ -0,0 +1,16 @@
const base = require('@playwright/test');
exports.test = base.test.extend({
page: async ({ page }, use) => {
global.page = page;
// Navigate to a real URL by default
// Playwright tests are executed on "about:blank" by default, which will
// cause operations that require the window location to be a valid URL to
// fail (e.g. AJAX requests). Navigating to a blank document with a real
// URL solved this problem.
await page.goto('/_blank.html');
await use(page);
},
});
exports.expect = base.expect;

View File

@ -1,28 +1,28 @@
const docsifyInit = require('../helpers/docsify-init');
const { test, expect } = require('./fixtures/docsify-init-fixture');
describe(`Index file hosting`, function() {
test.describe('Index file hosting', () => {
const sharedOptions = {
config: {
basePath: `${TEST_HOST}/docs/index.html#/`,
basePath: '/docs/index.html#/',
},
testURL: `${TEST_HOST}/docs/index.html#/`,
testURL: '/docs/index.html#/',
};
test('should serve from index file', async () => {
test('should serve from index file', async ({ page }) => {
await docsifyInit(sharedOptions);
await expect(page).toHaveText(
'#main',
await expect(page.locator('#main')).toContainText(
'A magical documentation site generator'
);
expect(page.url()).toMatch(/index\.html#\/$/);
});
test('should use index file links in sidebar from index file hosting', async () => {
test('should use index file links in sidebar from index file hosting', async ({
page,
}) => {
await docsifyInit(sharedOptions);
await page.click('a[href="#/quickstart"]');
await expect(page).toHaveText('#main', 'Quick start');
await expect(page.locator('#main')).toContainText('Quick start');
expect(page.url()).toMatch(/index\.html#\/quickstart$/);
});
});

View File

@ -1,11 +1,8 @@
const docsifyInit = require('../helpers/docsify-init');
const { test, expect } = require('./fixtures/docsify-init-fixture');
// Suite
// -----------------------------------------------------------------------------
describe('Search Plugin Tests', function() {
// Tests
// ---------------------------------------------------------------------------
test('search readme', async () => {
test.describe('Search Plugin Tests', () => {
test('search readme', async ({ page }) => {
const docsifyInitConfig = {
markdown: {
homepage: `
@ -27,15 +24,19 @@ describe('Search Plugin Tests', function() {
scriptURLs: ['/lib/plugins/search.min.js'],
};
const searchFieldElm = page.locator('input[type=search]');
const resultsHeadingElm = page.locator('.results-panel h2');
await docsifyInit(docsifyInitConfig);
await page.fill('input[type=search]', 'hello');
await expect(page).toEqualText('.results-panel h2', 'Hello World');
await searchFieldElm.fill('hello');
await expect(resultsHeadingElm).toHaveText('Hello World');
await page.click('.clear-button');
await page.fill('input[type=search]', 'test');
await expect(page).toEqualText('.results-panel h2', 'Test Page');
await searchFieldElm.fill('test');
await expect(resultsHeadingElm).toHaveText('Test Page');
});
test('search ignore title', async () => {
test('search ignore title', async ({ page }) => {
const docsifyInitConfig = {
markdown: {
homepage: `
@ -65,15 +66,20 @@ describe('Search Plugin Tests', function() {
},
scriptURLs: ['/lib/plugins/search.min.js'],
};
const searchFieldElm = page.locator('input[type=search]');
const resultsHeadingElm = page.locator('.results-panel h2');
await docsifyInit(docsifyInitConfig);
await page.fill('input[type=search]', 'repository1');
await expect(page).toEqualText('.results-panel h2', 'GitHub Pages ignore1');
await searchFieldElm.fill('repository1');
await expect(resultsHeadingElm).toHaveText('GitHub Pages ignore1');
await page.click('.clear-button');
await page.fill('input[type=search]', 'repository2');
await expect(page).toEqualText('.results-panel h2', 'GitHub Pages ignore2');
await searchFieldElm.fill('repository2');
await expect(resultsHeadingElm).toHaveText('GitHub Pages ignore2');
});
test('search only one homepage', async () => {
test('search only one homepage', async ({ page }) => {
const docsifyInitConfig = {
markdown: {
sidebar: `
@ -96,17 +102,21 @@ describe('Search Plugin Tests', function() {
scriptURLs: ['/lib/plugins/search.js'],
};
const searchFieldElm = page.locator('input[type=search]');
const resultsHeadingElm = page.locator('.results-panel h2');
const resultElm = page.locator('.matching-post');
await docsifyInit(docsifyInitConfig);
await page.fill('input[type=search]', 'hello');
await expect(page).toHaveSelector('.matching-post');
expect(await page.$$eval('.matching-post', elms => elms.length)).toBe(1);
await expect(page).toEqualText('.results-panel h2', 'Hello World');
await searchFieldElm.fill('hello');
await expect(resultElm).toHaveCount(1);
await expect(resultsHeadingElm).toHaveText('Hello World');
await page.click('.clear-button');
await page.fill('input[type=search]', 'test');
await expect(page).toEqualText('.results-panel h2', 'Test Page');
await searchFieldElm.fill('test');
await expect(resultsHeadingElm).toHaveText('Test Page');
});
test('search ignore diacritical marks', async () => {
test('search ignore diacritical marks', async ({ page }) => {
const docsifyInitConfig = {
markdown: {
homepage: `
@ -117,15 +127,20 @@ describe('Search Plugin Tests', function() {
},
scriptURLs: ['/lib/plugins/search.min.js'],
};
const searchFieldElm = page.locator('input[type=search]');
const resultsHeadingElm = page.locator('.results-panel h2');
await docsifyInit(docsifyInitConfig);
await page.fill('input[type=search]', 'documentacion');
await expect(page).toEqualText('.results-panel h2', 'Que es');
await searchFieldElm.fill('documentacion');
await expect(resultsHeadingElm).toHaveText('Que es');
await page.click('.clear-button');
await page.fill('input[type=search]', 'estáticos');
await expect(page).toEqualText('.results-panel h2', 'Que es');
await searchFieldElm.fill('estáticos');
await expect(resultsHeadingElm).toHaveText('Que es');
});
test('search when there is no title', async () => {
test('search when there is no title', async ({ page }) => {
const docsifyInitConfig = {
markdown: {
homepage: `
@ -146,14 +161,19 @@ describe('Search Plugin Tests', function() {
},
scriptURLs: ['/lib/plugins/search.min.js'],
};
const searchFieldElm = page.locator('input[type=search]');
const resultsHeadingElm = page.locator('.results-panel h2');
await docsifyInit(docsifyInitConfig);
await page.fill('input[type=search]', 'paragraph');
await expect(page).toEqualText('.results-panel h2', 'Home Page');
await searchFieldElm.fill('paragraph');
await expect(resultsHeadingElm).toHaveText('Home Page');
await page.click('.clear-button');
await page.fill('input[type=search]', 'Support');
await expect(page).toEqualText('.results-panel h2', 'changelog');
await searchFieldElm.fill('Support');
await expect(resultsHeadingElm).toHaveText('changelog');
await page.click('.clear-button');
await page.fill('input[type=search]', 'hello');
await expect(page).toEqualText('.results-panel h2', 'Changelog Title');
await searchFieldElm.fill('hello');
await expect(resultsHeadingElm).toHaveText('Changelog Title');
});
});

View File

@ -1,6 +1,7 @@
const docsifyInit = require('../helpers/docsify-init');
const { test, expect } = require('./fixtures/docsify-init-fixture');
describe(`Security`, function() {
test.describe('Security - Cross Site Scripting (XSS)', () => {
const sharedOptions = {
markdown: {
homepage: '# Hello World',
@ -9,24 +10,25 @@ describe(`Security`, function() {
'test.md': '# Test Page',
},
};
const slashStrings = ['//', '///'];
describe(`Cross Site Scripting (XSS)`, function() {
const slashStrings = ['//', '///'];
for (let slashString of slashStrings) {
const hash = `#${slashString}domain.com/file.md`;
for (const slashString of slashStrings) {
const hash = `#${slashString}domain.com/file.md`;
test(`should not load remote content from hash (${hash})`, async ({
page,
}) => {
const mainElm = page.locator('#main');
test(`should not load remote content from hash (${hash})`, async () => {
await docsifyInit(sharedOptions);
await expect(page).toHaveText('#main', 'Hello World');
await page.evaluate(() => (location.hash = '#/test'));
await expect(page).toHaveText('#main', 'Test Page');
await page.evaluate(newHash => {
location.hash = newHash;
}, hash);
await expect(page).toHaveText('#main', 'Hello World');
expect(page.url()).toMatch(/#\/$/);
});
}
});
await docsifyInit(sharedOptions);
await expect(mainElm).toContainText('Hello World');
await page.evaluate(() => (location.hash = '#/test'));
await expect(mainElm).toContainText('Test Page');
await page.evaluate(newHash => {
location.hash = newHash;
}, hash);
await expect(mainElm).toContainText('Hello World');
expect(page.url()).toMatch(/#\/$/);
});
}
});

View File

@ -1,11 +1,12 @@
const docsifyInit = require('../helpers/docsify-init');
const { test, expect } = require('./fixtures/docsify-init-fixture');
// Suite
// -----------------------------------------------------------------------------
describe('Sidebar Tests', function() {
test.describe('Sidebar Tests', () => {
// Tests
// ---------------------------------------------------------------------------
test('Active Test', async () => {
test('Active Test', async ({ page }) => {
const docsifyInitConfig = {
markdown: {
sidebar: `
@ -39,32 +40,32 @@ describe('Sidebar Tests', function() {
},
};
const activeLinkElm = page.locator('.sidebar-nav li[class=active]');
await docsifyInit(docsifyInitConfig);
await page.click('a[href="#/test%20space"]');
await expect(page).toEqualText(
'.sidebar-nav li[class=active]',
'Test Space'
);
await expect(activeLinkElm).toHaveText('Test Space');
expect(page.url()).toMatch(/\/test%20space$/);
await page.click('a[href="#/test_foo"]');
await expect(page).toEqualText('.sidebar-nav li[class=active]', 'Test _');
await expect(activeLinkElm).toHaveText('Test _');
expect(page.url()).toMatch(/\/test_foo$/);
await page.click('a[href="#/test-foo"]');
await expect(page).toEqualText('.sidebar-nav li[class=active]', 'Test -');
await expect(activeLinkElm).toHaveText('Test -');
expect(page.url()).toMatch(/\/test-foo$/);
await page.click('a[href="#/test.foo"]');
expect(page.url()).toMatch(/\/test.foo$/);
await expect(page).toEqualText('.sidebar-nav li[class=active]', 'Test .');
await expect(activeLinkElm).toHaveText('Test .');
await page.click('a[href="#/test>foo"]');
await expect(page).toEqualText('.sidebar-nav li[class=active]', 'Test >');
await expect(activeLinkElm).toHaveText('Test >');
expect(page.url()).toMatch(/\/test%3Efoo$/);
await page.click('a[href="#/test"]');
await expect(page).toEqualText('.sidebar-nav li[class=active]', 'Test');
await expect(activeLinkElm).toHaveText('Test');
expect(page.url()).toMatch(/\/test$/);
});
});

View File

@ -1,12 +1,13 @@
const stripIndent = require('common-tags/lib/stripIndent');
const docsifyInit = require('../helpers/docsify-init');
const { test, expect } = require('./fixtures/docsify-init-fixture');
const vueURLs = [
'/node_modules/vue2/dist/vue.js',
'/node_modules/vue3/dist/vue.global.js',
];
describe('Vue.js Compatibility', function() {
test.describe('Vue.js Compatibility', () => {
function getSharedConfig() {
const config = {
config: {
@ -15,7 +16,7 @@ describe('Vue.js Compatibility', function() {
template: `
<button @click="counter++">{{ counter }}</button>
`,
data: function() {
data: function () {
return {
counter: 0,
};
@ -23,7 +24,7 @@ describe('Vue.js Compatibility', function() {
},
},
vueGlobalOptions: {
data: function() {
data: function () {
return {
counter: 0,
msg: 'vueglobaloptions',
@ -32,7 +33,7 @@ describe('Vue.js Compatibility', function() {
},
vueMounts: {
'#vuemounts': {
data: function() {
data: function () {
return {
counter: 0,
msg: 'vuemounts',
@ -96,9 +97,11 @@ describe('Vue.js Compatibility', function() {
for (const vueURL of vueURLs) {
const vueVersion = Number(vueURL.match(/vue(\d+)/)[1]); // 2|3
describe(`Vue v${vueVersion}`, function() {
test.describe(`Vue v${vueVersion}`, () => {
for (const executeScript of [true, undefined]) {
test(`renders content when executeScript is ${executeScript}`, async () => {
test(`renders content when executeScript is ${executeScript}`, async ({
page,
}) => {
const docsifyInitConfig = getSharedConfig();
docsifyInitConfig.config.executeScript = executeScript;
@ -107,45 +110,48 @@ describe('Vue.js Compatibility', function() {
await docsifyInit(docsifyInitConfig);
// Static
await expect(page).toEqualText('#vuefor', '12345');
await expect(page).toEqualText('#vuecomponent', '0');
await expect(page).toEqualText(
'#vueglobaloptions p',
await expect(page.locator('#vuefor')).toHaveText('12345');
await expect(page.locator('#vuecomponent')).toHaveText('0');
await expect(page.locator('#vueglobaloptions p')).toHaveText(
'vueglobaloptions'
);
await expect(page).toEqualText('#vueglobaloptions span', '0');
await expect(page).toEqualText('#vuemounts p', 'vuemounts');
await expect(page).toEqualText('#vuemounts span', '0');
await expect(page).toEqualText('#vuescript p', 'vuescript');
await expect(page).toEqualText('#vuescript span', '0');
await expect(page.locator('#vueglobaloptions > span')).toHaveText(
'0'
);
await expect(page.locator('#vuemounts p')).toHaveText('vuemounts');
await expect(page.locator('#vuemounts > span')).toHaveText('0');
await expect(page.locator('#vuescript p')).toHaveText('vuescript');
await expect(page.locator('#vuescript > span')).toHaveText('0');
// Reactive
await page.click('#vuecomponent');
await expect(page).toEqualText('#vuecomponent', '1');
await expect(page.locator('#vuecomponent')).toHaveText('1');
await page.click('#vueglobaloptions button');
await expect(page).toEqualText('#vueglobaloptions span', '1');
await expect(page.locator('#vueglobaloptions > span')).toHaveText(
'1'
);
await page.click('#vuemounts button');
await expect(page).toEqualText('#vuemounts span', '1');
await expect(page.locator('#vuemounts > span')).toHaveText('1');
await page.click('#vuescript button');
await expect(page).toEqualText('#vuescript span', '1');
await expect(page.locator('#vuescript > span')).toHaveText('1');
});
}
test(`ignores content when Vue is not present`, async () => {
test(`ignores content when Vue is not present`, async ({ page }) => {
const docsifyInitConfig = getSharedConfig();
await docsifyInit(docsifyInitConfig);
await page.evaluate(() => {
return 'Vue' in window === false;
});
await expect(page).toEqualText('#vuefor', '{{ i }}');
await expect(page).toEqualText('#vuecomponent', '---');
await expect(page).toEqualText('#vueglobaloptions p', '---');
await expect(page).toEqualText('#vuemounts p', '---');
await expect(page).toEqualText('#vuescript p', '---');
await page.evaluate(() => 'Vue' in window === false);
await expect(page.locator('#vuefor')).toHaveText('{{ i }}');
await expect(page.locator('#vuecomponent')).toHaveText('---');
await expect(page.locator('#vueglobaloptions p')).toHaveText('---');
await expect(page.locator('#vuemounts p')).toHaveText('---');
await expect(page.locator('#vuescript p')).toHaveText('---');
});
test(`ignores content when vueComponents, vueMounts, and vueGlobalOptions are undefined`, async () => {
test(`ignores content when vueComponents, vueMounts, and vueGlobalOptions are undefined`, async ({
page,
}) => {
const docsifyInitConfig = getSharedConfig();
docsifyInitConfig.config.vueComponents = undefined;
@ -154,52 +160,58 @@ describe('Vue.js Compatibility', function() {
docsifyInitConfig.scriptURLs = vueURL;
await docsifyInit(docsifyInitConfig);
await expect(page).toEqualText('#vuefor', '{{ i }}');
await expect(page).toEqualText('#vuecomponent', '---');
await expect(page).toEqualText('#vueglobaloptions p', '---');
await expect(page).toEqualText('#vuemounts p', '---');
await expect(page).toEqualText('#vuescript p', 'vuescript');
await expect(page.locator('#vuefor')).toHaveText('{{ i }}');
await expect(page.locator('#vuecomponent')).toHaveText('---');
await expect(page.locator('#vueglobaloptions p')).toHaveText('---');
await expect(page.locator('#vuemounts p')).toHaveText('---');
await expect(page.locator('#vuescript p')).toHaveText('vuescript');
});
test(`ignores content when vueGlobalOptions is undefined`, async () => {
test(`ignores content when vueGlobalOptions is undefined`, async ({
page,
}) => {
const docsifyInitConfig = getSharedConfig();
docsifyInitConfig.config.vueGlobalOptions = undefined;
docsifyInitConfig.scriptURLs = vueURL;
await docsifyInit(docsifyInitConfig);
await expect(page).toEqualText('#vuefor', '12345');
await expect(page).toEqualText('#vuecomponent', '0');
expect(await page.innerText('#vueglobaloptions p')).toBe('');
await expect(page).toEqualText('#vuemounts p', 'vuemounts');
await expect(page).toEqualText('#vuescript p', 'vuescript');
await expect(page.locator('#vuefor')).toHaveText('12345');
await expect(page.locator('#vuecomponent')).toHaveText('0');
await expect(page.locator('#vuecomponent')).toHaveText('0');
expect(await page.locator('#vueglobaloptions p').innerText()).toBe('');
await expect(page.locator('#vuemounts p')).toHaveText('vuemounts');
await expect(page.locator('#vuescript p')).toHaveText('vuescript');
});
test(`ignores content when vueMounts is undefined`, async () => {
test(`ignores content when vueMounts is undefined`, async ({ page }) => {
const docsifyInitConfig = getSharedConfig();
docsifyInitConfig.config.vueMounts['#vuemounts'] = undefined;
docsifyInitConfig.scriptURLs = vueURL;
await docsifyInit(docsifyInitConfig);
await expect(page).toEqualText('#vuefor', '12345');
await expect(page).toEqualText('#vuecomponent', '0');
await expect(page).toEqualText(
'#vueglobaloptions p',
await expect(page.locator('#vuefor')).toHaveText('12345');
await expect(page.locator('#vuecomponent')).toHaveText('0');
await expect(page.locator('#vueglobaloptions p')).toHaveText(
'vueglobaloptions'
);
await expect(page).toEqualText('#vuemounts p', 'vueglobaloptions');
await expect(page).toEqualText('#vuescript p', 'vuescript');
await expect(page.locator('#vuemounts p')).toHaveText(
'vueglobaloptions'
);
await expect(page.locator('#vuescript p')).toHaveText('vuescript');
});
test(`ignores <script> when executeScript is false`, async () => {
test(`ignores <script> when executeScript is false`, async ({ page }) => {
const docsifyInitConfig = getSharedConfig();
docsifyInitConfig.config.executeScript = false;
docsifyInitConfig.scriptURLs = vueURL;
await docsifyInit(docsifyInitConfig);
await expect(page).toEqualText('#vuescript p', 'vueglobaloptions');
await expect(page.locator('#vuescript p')).toHaveText(
'vueglobaloptions'
);
});
});
}

View File

@ -1,15 +1,14 @@
/* global jestPlaywright page */
import mock, { proxy } from 'xhr-mock';
import { waitForSelector } from './wait-for';
/* globals page */
const axios = require('axios');
const mock = require('xhr-mock').default;
const prettier = require('prettier');
const stripIndent = require('common-tags/lib/stripIndent');
const { proxy } = require('xhr-mock');
const { waitForSelector } = require('./wait-for');
const docsifyPATH = '../../lib/docsify.js'; // JSDOM
const docsifyURL = '/lib/docsify.js'; // Playwright
const isJSDOM = 'window' in global;
const isPlaywright = 'page' in global;
/**
* Jest / Playwright helper for creating custom docsify test sites
@ -28,17 +27,19 @@ const isPlaywright = 'page' in global;
* @param {String} [options.style] CSS to inject via <style> tag
* @param {String|String[]} [options.styleURLs=['/lib/themes/vue.css']] External CSS to inject via <link rel="stylesheet"> tag(s)
* @param {String} [options.testURL] URL to set as window.location.href
* @param {String} [options.waitForSelector='#main'] Element to wait for before returning promsie
* @param {String} [options._debug] initiate debugger after site is created
* @param {String} [options.waitForSelector='#main'] Element to wait for before returning promise
* @param {Boolean|Object|String} [options._logHTML] Logs HTML to console after initialization. Accepts CSS selector.
* @param {Boolean} [options._logHTML.format=true] Formats HTML output
* @param {String} [options._logHTML.selector='html'] CSS selector(s) to match and log HTML for
* @returns {Promise}
*/
async function docsifyInit(options = {}) {
const isJSDOM = 'window' in global;
const isPlaywright = 'page' in global;
const defaults = {
config: {
basePath: TEST_HOST,
basePath: process.env.TEST_HOST,
el: '#app',
},
html: `
@ -63,7 +64,7 @@ async function docsifyInit(options = {}) {
scriptURLs: [],
style: '',
styleURLs: [],
testURL: `${TEST_HOST}/docsify-init.html`,
testURL: `${process.env.TEST_HOST}/docsify-init.html`,
waitForSelector: '#main > *',
};
const settings = {
@ -80,13 +81,16 @@ async function docsifyInit(options = {}) {
const updateBasePath = config => {
if (config.basePath) {
config.basePath = new URL(config.basePath, TEST_HOST).href;
config.basePath = new URL(
config.basePath,
process.env.TEST_HOST
).href;
}
};
// Config as function
if (typeof options.config === 'function') {
return function(vm) {
return function (vm) {
const config = { ...sharedConfig, ...options.config(vm) };
updateBasePath(config);
@ -131,7 +135,8 @@ async function docsifyInit(options = {}) {
.filter(([url, responseText]) => url && responseText)
.map(([url, responseText]) => [
// Convert relative to absolute URL
new URL(url, settings.config.basePath || TEST_HOST).href,
new URL(url, settings.config.basePath || process.env.TEST_HOST)
.href,
// Strip indentation from responseText
stripIndent`${responseText}`,
])
@ -143,11 +148,11 @@ async function docsifyInit(options = {}) {
scriptURLs: []
.concat(options.scriptURLs || '')
.filter(url => url)
.map(url => new URL(url, TEST_HOST).href),
.map(url => new URL(url, process.env.TEST_HOST).href),
styleURLs: []
.concat(options.styleURLs || '')
.filter(url => url)
.map(url => new URL(url, TEST_HOST).href),
.map(url => new URL(url, process.env.TEST_HOST).href),
};
// Routes
@ -325,7 +330,7 @@ async function docsifyInit(options = {}) {
elm => elm.outerHTML
);
} else {
htmlArr = await page.$$eval(selector, elms =>
htmlArr = await page.evaluateAll(selector, elms =>
elms.map(e => e.outerHTML)
);
}
@ -350,16 +355,6 @@ async function docsifyInit(options = {}) {
}
}
// Debug
if (settings._debug) {
if (isJSDOM) {
// eslint-disable-next-line no-debugger
debugger;
} else if (isPlaywright) {
await jestPlaywright.debug();
}
}
return Promise.resolve();
}

View File

@ -11,7 +11,7 @@ const defaults = {
* @param {Object} options optional parameters
* @returns {Promise} promise which resolves to function result
*/
export function waitForFunction(fn, arg, options = {}) {
function waitForFunction(fn, arg, options = {}) {
const settings = {
...defaults,
...options,
@ -54,7 +54,7 @@ export function waitForFunction(fn, arg, options = {}) {
* @param {Object} options optional parameters
* @returns {Promise} promise which resolves to first matching element
*/
export function waitForSelector(cssSelector, options = {}) {
function waitForSelector(cssSelector, options = {}) {
const settings = {
...defaults,
...options,
@ -89,7 +89,7 @@ export function waitForSelector(cssSelector, options = {}) {
* @param {Object} options optional parameters
* @returns {Promise} promise which resolves to first matching element that contains specified text
*/
export function waitForText(cssSelector, text, options = {}) {
function waitForText(cssSelector, text, options = {}) {
const settings = {
...defaults,
...options,
@ -124,3 +124,9 @@ export function waitForText(cssSelector, text, options = {}) {
});
});
}
module.exports = {
waitForFunction,
waitForSelector,
waitForText,
};

View File

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

View File

@ -2,7 +2,7 @@ const docsifyInit = require('../helpers/docsify-init');
// Suite
// -----------------------------------------------------------------------------
describe('Docs Site', function() {
describe('Docs Site', function () {
// Tests
// ---------------------------------------------------------------------------
test('coverpage renders and is unchanged', async () => {

View File

@ -2,13 +2,13 @@ const docsifyInit = require('../helpers/docsify-init');
// Suite
// -----------------------------------------------------------------------------
describe('Docsify', function() {
describe('Docsify', function () {
// Tests
// ---------------------------------------------------------------------------
test('allows $docsify configuration to be a function', async () => {
const testConfig = jest.fn(vm => {
expect(vm).toBeInstanceOf(Object);
expect(vm.constructor.name).toEqual('Docsify');
expect(vm.constructor.name).toBe('Docsify');
expect(vm.$fetch).toBeInstanceOf(Function);
expect(vm.$resetEvents).toBeInstanceOf(Function);
expect(vm.route).toBeInstanceOf(Object);
@ -18,7 +18,7 @@ describe('Docsify', function() {
config: testConfig,
});
expect(typeof Docsify).toEqual('object');
expect(typeof Docsify).toBe('object');
expect(testConfig).toHaveBeenCalled();
});
@ -28,7 +28,7 @@ describe('Docsify', function() {
return {
plugins: [
function(hook, vm2) {
function (hook, vm2) {
expect(vm1).toEqual(vm2);
expect(hook.init).toBeInstanceOf(Function);
@ -46,7 +46,7 @@ describe('Docsify', function() {
config: testConfig,
});
expect(typeof Docsify).toEqual('object');
expect(typeof Docsify).toBe('object');
expect(testConfig).toHaveBeenCalled();
});
});

View File

@ -1,10 +1,10 @@
import { waitForFunction, waitForText } from '../helpers/wait-for';
const { waitForFunction, waitForText } = require('../helpers/wait-for');
const docsifyInit = require('../helpers/docsify-init');
// Suite
// -----------------------------------------------------------------------------
describe('Example Tests', function() {
describe('Example Tests', function () {
// Tests
// ---------------------------------------------------------------------------
test('Docsify /docs/ site using docsifyInit()', async () => {
@ -16,7 +16,7 @@ describe('Example Tests', function() {
});
// Verify config options
expect(typeof window.$docsify).toEqual('object');
expect(typeof window.$docsify).toBe('object');
// Verify options.markdown content was rendered
expect(document.querySelector('#main').textContent).toContain(
@ -83,7 +83,7 @@ describe('Example Tests', function() {
});
// Verify config options
expect(typeof window.$docsify).toEqual('object');
expect(typeof window.$docsify).toBe('object');
expect(window.$docsify).toHaveProperty('themeColor', 'red');
expect(document.querySelector('.app-name').textContent).toContain(
'Docsify Name'

View File

@ -2,20 +2,20 @@ import initGlobalAPI from '../../src/core/global-api.js';
// Suite
// -----------------------------------------------------------------------------
describe('Global APIs', function() {
describe('Global APIs', function () {
// Tests
// ---------------------------------------------------------------------------
test('APIs are available', () => {
initGlobalAPI();
expect(typeof window.Docsify).toEqual('object');
expect(typeof window.Docsify.util).toEqual('object');
expect(typeof window.Docsify.dom).toEqual('object');
expect(typeof window.Docsify.get).toEqual('function');
expect(typeof window.Docsify.slugify).toEqual('function');
expect(typeof window.Docsify.version).toEqual('string');
expect(typeof window.DocsifyCompiler).toEqual('function');
expect(typeof window.marked).toEqual('function');
expect(typeof window.Prism).toEqual('object');
expect(typeof window.Docsify).toBe('object');
expect(typeof window.Docsify.util).toBe('object');
expect(typeof window.Docsify.dom).toBe('object');
expect(typeof window.Docsify.get).toBe('function');
expect(typeof window.Docsify.slugify).toBe('function');
expect(typeof window.Docsify.version).toBe('string');
expect(typeof window.DocsifyCompiler).toBe('function');
expect(typeof window.marked).toBe('function');
expect(typeof window.Prism).toBe('object');
});
});

View File

@ -3,7 +3,7 @@ const docsifyInit = require('../helpers/docsify-init');
// Suite
// -----------------------------------------------------------------------------
describe('render', function() {
describe('render', function () {
// Setup & Teardown
// -------------------------------------------------------------------------
beforeEach(async () => {
@ -32,8 +32,8 @@ describe('render', function() {
// Lists
// ---------------------------------------------------------------------------
describe('lists', function() {
test('as unordered task list', async function() {
describe('lists', function () {
test('as unordered task list', async function () {
const output = window.marked(stripIndent`
- [x] Task 1
- [ ] Task 2
@ -45,7 +45,7 @@ describe('render', function() {
);
});
test('as ordered task list', async function() {
test('as ordered task list', async function () {
const output = window.marked(stripIndent`
1. [ ] Task 1
2. [x] Task 2
@ -56,7 +56,7 @@ describe('render', function() {
);
});
test('normal unordered', async function() {
test('normal unordered', async function () {
const output = window.marked(stripIndent`
- [linktext](link)
- just text
@ -67,7 +67,7 @@ describe('render', function() {
);
});
test('unordered with custom start', async function() {
test('unordered with custom start', async function () {
const output = window.marked(stripIndent`
1. first
2. second
@ -82,7 +82,7 @@ describe('render', function() {
);
});
test('nested', async function() {
test('nested', async function () {
const output = window.marked(stripIndent`
- 1
- 2
@ -99,8 +99,8 @@ describe('render', function() {
// Images
// ---------------------------------------------------------------------------
describe('images', function() {
test('regular', async function() {
describe('images', function () {
test('regular', async function () {
const output = window.marked('![alt text](http://imageUrl)');
expect(output).toMatchInlineSnapshot(
@ -108,7 +108,7 @@ describe('render', function() {
);
});
test('class', async function() {
test('class', async function () {
const output = window.marked(
"![alt text](http://imageUrl ':class=someCssClass')"
);
@ -118,7 +118,7 @@ describe('render', function() {
);
});
test('id', async function() {
test('id', async function () {
const output = window.marked(
"![alt text](http://imageUrl ':id=someCssID')"
);
@ -128,7 +128,7 @@ describe('render', function() {
);
});
test('no-zoom', async function() {
test('no-zoom', async function () {
const output = window.marked("![alt text](http://imageUrl ':no-zoom')");
expect(output).toMatchInlineSnapshot(
@ -136,8 +136,8 @@ describe('render', function() {
);
});
describe('size', function() {
test('width and height', async function() {
describe('size', function () {
test('width and height', async function () {
const output = window.marked(
"![alt text](http://imageUrl ':size=WIDTHxHEIGHT')"
);
@ -147,7 +147,7 @@ describe('render', function() {
);
});
test('width', async function() {
test('width', async function () {
const output = window.marked("![alt text](http://imageUrl ':size=50')");
expect(output).toMatchInlineSnapshot(
@ -159,8 +159,8 @@ describe('render', function() {
// Headings
// ---------------------------------------------------------------------------
describe('headings', function() {
test('h1', async function() {
describe('headings', function () {
test('h1', async function () {
const output = window.marked('# h1 tag');
expect(output).toMatchInlineSnapshot(
@ -168,7 +168,7 @@ describe('render', function() {
);
});
test('h2', async function() {
test('h2', async function () {
const output = window.marked('## h2 tag');
expect(output).toMatchInlineSnapshot(
@ -176,7 +176,7 @@ describe('render', function() {
);
});
test('h3', async function() {
test('h3', async function () {
const output = window.marked('### h3 tag');
expect(output).toMatchInlineSnapshot(
@ -184,7 +184,7 @@ describe('render', function() {
);
});
test('h4', async function() {
test('h4', async function () {
const output = window.marked('#### h4 tag');
expect(output).toMatchInlineSnapshot(
@ -192,7 +192,7 @@ describe('render', function() {
);
});
test('h5', async function() {
test('h5', async function () {
const output = window.marked('##### h5 tag');
expect(output).toMatchInlineSnapshot(
@ -200,7 +200,7 @@ describe('render', function() {
);
});
test('h6', async function() {
test('h6', async function () {
const output = window.marked('###### h6 tag');
expect(output).toMatchInlineSnapshot(
@ -209,8 +209,8 @@ describe('render', function() {
});
});
describe('link', function() {
test('regular', async function() {
describe('link', function () {
test('regular', async function () {
const output = window.marked('[alt text](http://url)');
expect(output).toMatchInlineSnapshot(
@ -218,7 +218,7 @@ describe('render', function() {
);
});
test('linkrel', async function() {
test('linkrel', async function () {
// const { docsify } = await init('default', {
// externalLinkTarget: '_blank',
// externalLinkRel: 'noopener',
@ -230,7 +230,7 @@ describe('render', function() {
);
});
test('disabled', async function() {
test('disabled', async function () {
const output = window.marked("[alt text](http://url ':disabled')");
expect(output).toMatchInlineSnapshot(
@ -238,7 +238,7 @@ describe('render', function() {
);
});
test('target', async function() {
test('target', async function () {
const output = window.marked("[alt text](http://url ':target=_self')");
expect(output).toMatchInlineSnapshot(
@ -246,7 +246,7 @@ describe('render', function() {
);
});
test('class', async function() {
test('class', async function () {
const output = window.marked(
"[alt text](http://url ':class=someCssClass')"
);
@ -256,7 +256,7 @@ describe('render', function() {
);
});
test('id', async function() {
test('id', async function () {
const output = window.marked("[alt text](http://url ':id=someCssID')");
expect(output).toMatchInlineSnapshot(

View File

@ -1,10 +1,7 @@
const jestConfig = require('../jest.config.js');
module.exports = {
env: {
'jest/globals': true,
},
extends: ['plugin:jest/recommended', 'plugin:jest/style'],
globals: jestConfig.globals,
plugins: ['jest'],
};

View File

@ -1,13 +1,18 @@
//
import { greet } from './fixtures/greet.js';
import { getTimeOfDay } from './fixtures/get-time-of-day.js';
import * as getTimeOfDayModule from './fixtures/get-time-of-day.js';
// const greet = require('./fixtures/greet');
// const getTimeOfDay = require('./fixtures/get-time-of-day');
// const getTimeOfDayModule = { getTimeOfDay: getTimeOfDay };
// Suite
// -----------------------------------------------------------------------------
describe(`Example Tests`, function() {
describe(`Example Tests`, function () {
// Tests
// ---------------------------------------------------------------------------
describe('Jest & JSDOM basics', function() {
describe('Jest & JSDOM basics', function () {
test('dom manipulation (jsdom)', () => {
const testText = 'This is a test';
const testHTML = `<h1>Test</h1><p>${testText}</p>`;
@ -19,7 +24,7 @@ describe(`Example Tests`, function() {
document.body.classList.add('foo');
// Test HTML
expect(document.body.getAttribute('class')).toEqual('foo');
expect(document.body.getAttribute('class')).toBe('foo');
expect(document.body.textContent).toMatch(/Test/);
expect(document.querySelectorAll('p')).toHaveLength(1);
expect(document.querySelector('p').textContent).toBe(testText);
@ -48,11 +53,11 @@ describe(`Example Tests`, function() {
});
});
describe('Fake Timers', function() {
describe('Fake Timers', function () {
test('data & time', () => {
const fakeDate = new Date().setHours(1);
jest.useFakeTimers('modern');
jest.useFakeTimers();
jest.setSystemTime(fakeDate);
const timeOfDay = getTimeOfDay();
@ -61,7 +66,7 @@ describe(`Example Tests`, function() {
});
});
describe('Mocks & Spys', function() {
describe('Mocks & Spies', function () {
test('mock import/require dependency using jest.fn()', () => {
const testModule = require('./fixtures/get-time-of-day.js');
const { greet: testGreet } = require('./fixtures/greet.js');
@ -97,9 +102,9 @@ describe(`Example Tests`, function() {
// Replace Math.random() implementation to return fixed value
jest.spyOn(Math, 'random').mockImplementation(() => 0.1);
expect(Math.random()).toEqual(0.1);
expect(Math.random()).toEqual(0.1);
expect(Math.random()).toEqual(0.1);
expect(Math.random()).toBe(0.1);
expect(Math.random()).toBe(0.1);
expect(Math.random()).toBe(0.1);
});
test('spy on import/require dependency using jest.spyOn()', () => {

View File

@ -16,7 +16,7 @@ describe('core/render/utils', () => {
test('removeAtag from a link', () => {
const result = removeAtag('<a href="www.example.com">content</a>');
expect(result).toEqual('content');
expect(result).toBe('content');
});
});
@ -82,7 +82,7 @@ describe('core/render/tpl', () => {
},
]);
expect(result).toEqual(
expect(result).toBe(
`<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>`
);
});
@ -96,7 +96,7 @@ describe('core/render/slugify', () => {
const result2 = slugify(
`Another <span style="font-size: 1.2em" class="foo bar baz">broken <span class="aaa">example</span></span>`
);
expect(result).toEqual(`bla-bla-bla-`);
expect(result2).toEqual(`another-broken-example`);
expect(result).toBe(`bla-bla-bla-`);
expect(result2).toBe(`another-broken-example`);
});
});

View File

@ -27,25 +27,25 @@ describe('router/history/base', () => {
test('toURL', () => {
const url = history.toURL('guide.md', {}, '/zh-ch/');
expect(url).toEqual('/zh-ch/guide');
expect(url).toBe('/zh-ch/guide');
});
test('toURL with double dot', () => {
const url = history.toURL('../README.md', {}, '/zh-ch/');
expect(url).toEqual('/README');
expect(url).toBe('/README');
});
test('toURL child path', () => {
const url = history.toURL('config/example.md', {}, '/zh-ch/');
expect(url).toEqual('/zh-ch/config/example');
expect(url).toBe('/zh-ch/config/example');
});
test('toURL absolute path', () => {
const url = history.toURL('/README', {}, '/zh-ch/');
expect(url).toEqual('/README');
expect(url).toBe('/README');
});
});
@ -63,7 +63,7 @@ describe('router/history/base', () => {
test('toURL', () => {
const url = history.toURL('README', {}, '/zh-ch/');
expect(url).toEqual('/README');
expect(url).toBe('/README');
});
});
});

View File

@ -9,19 +9,19 @@ describe('router/util', () => {
test('resolvePath with filename', () => {
const result = resolvePath('hello.md');
expect(result).toEqual('/hello.md');
expect(result).toBe('/hello.md');
});
test('resolvePath with ./', () => {
const result = resolvePath('./hello.md');
expect(result).toEqual('/hello.md');
expect(result).toBe('/hello.md');
});
test('resolvePath with ../', () => {
const result = resolvePath('test/../hello.md');
expect(result).toEqual('/hello.md');
expect(result).toBe('/hello.md');
});
});
});