mirror of
https://github.com/docsifyjs/docsify.git
synced 2025-12-08 19:55:52 +00:00
Merge pull request #1409 from docsifyjs/feat/vue-options
feat: Vue components, mount options, global options, and v3 support
This commit is contained in:
commit
bd662c421a
@ -1,3 +0,0 @@
|
||||
{
|
||||
"video": false
|
||||
}
|
||||
@ -245,10 +245,10 @@ window.$docsify = {
|
||||
// Custom file name
|
||||
coverpage: 'cover.md',
|
||||
|
||||
// mutiple covers
|
||||
// multiple covers
|
||||
coverpage: ['/', '/zh-cn/'],
|
||||
|
||||
// mutiple covers and custom file name
|
||||
// multiple covers and custom file name
|
||||
coverpage: {
|
||||
'/': 'cover.md',
|
||||
'/zh-cn/': 'cover.md',
|
||||
@ -410,7 +410,7 @@ window.$docsify = {
|
||||
};
|
||||
```
|
||||
|
||||
?> If this options is `false` but you dont want to emojify some specific colons , [Refer this](https://github.com/docsifyjs/docsify/issues/742#issuecomment-586313143)
|
||||
?> If this options is `false` but you don't want to emojify some specific colons , [Refer this](https://github.com/docsifyjs/docsify/issues/742#issuecomment-586313143)
|
||||
|
||||
## mergeNavbar
|
||||
|
||||
@ -494,15 +494,15 @@ window.$docsify = {
|
||||
```
|
||||
|
||||
## crossOriginLinks
|
||||
- type: `Array`
|
||||
|
||||
When `routerMode: 'history'`, you may face the cross-origin issues, See [#1379](https://github.com/docsifyjs/docsify/issues/1379).
|
||||
In Markdown content, there is a simple way to solve it, see extends Markdown syntax `Cross-Origin link` in [helpers](helpers.md).
|
||||
- type: `Array`
|
||||
|
||||
When `routerMode: 'history'`, you may face the cross-origin issues, See [#1379](https://github.com/docsifyjs/docsify/issues/1379).
|
||||
In Markdown content, there is a simple way to solve it, see extends Markdown syntax `Cross-Origin link` in [helpers](helpers.md).
|
||||
|
||||
```js
|
||||
window.$docsify = {
|
||||
crossOriginLinks:[
|
||||
"https://example.com/cross-origin-link",
|
||||
],
|
||||
crossOriginLinks: ['https://example.com/cross-origin-link'],
|
||||
};
|
||||
```
|
||||
|
||||
@ -604,7 +604,7 @@ window.$docsify = {
|
||||
};
|
||||
```
|
||||
|
||||
Load the right 404 page according to the localisation:
|
||||
Load the right 404 page according to the localization:
|
||||
|
||||
```js
|
||||
window.$docsify = {
|
||||
@ -629,3 +629,104 @@ window.$docsify = {
|
||||
topMargin: 90, // default: 0
|
||||
};
|
||||
```
|
||||
|
||||
## vueComponents
|
||||
|
||||
- type: `Object`
|
||||
|
||||
Creates and registers global [Vue components](https://vuejs.org/v2/guide/components.html). Components are specified using the component name as the key with an object containing Vue options as the value. Component `data` is unique for each instance and will not persist as users navigate the site.
|
||||
|
||||
```js
|
||||
window.$docsify = {
|
||||
vueComponents: {
|
||||
'button-counter': {
|
||||
template: `
|
||||
<button @click="count += 1">
|
||||
You clicked me {{ count }} times
|
||||
</button>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
count: 0,
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
```markdown
|
||||
<button-counter></button-counter>
|
||||
```
|
||||
|
||||
<output data-lang="output">
|
||||
<button-counter></button-counter>
|
||||
</output>
|
||||
|
||||
## vueGlobalOptions
|
||||
|
||||
- type: `Object`
|
||||
|
||||
Specifies [Vue options](https://vuejs.org/v2/api/#Options-Data) for use with Vue content not explicitly mounted with [vueMounts](#mounting-dom-elements), [vueComponents](#components), or a [markdown script](#markdown-script). Changes to global `data` will persist and be reflected anywhere global references are used.
|
||||
|
||||
```js
|
||||
window.$docsify = {
|
||||
vueGlobalOptions: {
|
||||
data() {
|
||||
return {
|
||||
count: 0,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
```markdown
|
||||
<p>
|
||||
<button @click="count -= 1">-</button>
|
||||
{{ count }}
|
||||
<button @click="count += 1">+</button>
|
||||
</p>
|
||||
```
|
||||
|
||||
<output data-lang="output">
|
||||
<p>
|
||||
<button @click="count -= 1">-</button>
|
||||
{{ count }}
|
||||
<button @click="count += 1">+</button>
|
||||
</p>
|
||||
</output>
|
||||
|
||||
## vueMounts
|
||||
|
||||
- type: `Object`
|
||||
|
||||
Specifies DOM elements to mount as [Vue instances](https://vuejs.org/v2/guide/instance.html) and their associated options. Mount elements are specified using a [CSS selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) as the key with an object containing Vue options as their value. Docsify will mount the first matching element in the main content area each time a new page is loaded. Mount element `data` is unique for each instance and will not persist as users navigate the site.
|
||||
|
||||
```js
|
||||
window.$docsify = {
|
||||
vueMounts: {
|
||||
'#counter': {
|
||||
data() {
|
||||
return {
|
||||
count: 0,
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
```markdown
|
||||
<div id="counter">
|
||||
<button @click="count -= 1">-</button>
|
||||
{{ count }}
|
||||
<button @click="count += 1">+</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
<output id="counter">
|
||||
<button @click="count -= 1">-</button>
|
||||
{{ count }}
|
||||
<button @click="count += 1">+</button>
|
||||
</output>
|
||||
|
||||
@ -101,6 +101,62 @@
|
||||
'/': 'Search',
|
||||
},
|
||||
},
|
||||
vueComponents: {
|
||||
'button-counter': {
|
||||
template: `
|
||||
<button @click="count += 1">
|
||||
You clicked me {{ count }} times
|
||||
</button>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
count: 0,
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
vueGlobalOptions: {
|
||||
data() {
|
||||
return {
|
||||
count: 0,
|
||||
message: 'Hello, World!',
|
||||
// Fake API response
|
||||
images: [
|
||||
{ title: 'Image 1', url: 'https://picsum.photos/150?random=1' },
|
||||
{ title: 'Image 2', url: 'https://picsum.photos/150?random=2' },
|
||||
{ title: 'Image 3', url: 'https://picsum.photos/150?random=3' },
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
timeOfDay() {
|
||||
const date = new Date();
|
||||
const hours = date.getHours();
|
||||
|
||||
if (hours < 12) {
|
||||
return 'morning';
|
||||
} else if (hours < 18) {
|
||||
return 'afternoon';
|
||||
} else {
|
||||
return 'evening';
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hello: function() {
|
||||
alert(this.message);
|
||||
},
|
||||
},
|
||||
},
|
||||
vueMounts: {
|
||||
'#counter': {
|
||||
data() {
|
||||
return {
|
||||
count: 0,
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
function(hook, vm) {
|
||||
hook.beforeEach(function(html) {
|
||||
@ -163,5 +219,6 @@
|
||||
})();
|
||||
</script>
|
||||
<script src="//cdn.jsdelivr.net/npm/vue@2/dist/vue.min.js"></script>
|
||||
<!-- <script src="//cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js"></script> -->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
371
docs/vue.md
371
docs/vue.md
@ -1,8 +1,8 @@
|
||||
# Vue compatibility
|
||||
|
||||
Docsify allows Vue [v2.x](https://vuejs.org) and [v3.x](https://v3.vuejs.org) components to be added directly to you Markdown files. These components can greatly simplify working with data and adding reactivity to your content.
|
||||
Docsify allows Vue content to be added directly to you markdown pages. This can greatly simplify working with data and adding reactivity to your site.
|
||||
|
||||
To get started, load either the production or development version of Vue in your `index.html`:
|
||||
To get started, add Vue [2.x](https://vuejs.org) or [3.x](https://v3.vuejs.org) to your `index.html` file. Choose the production version for your live site or the development version for helpful console warnings and [Vue.js devtools](https://github.com/vuejs/vue-devtools) support.
|
||||
|
||||
#### Vue 2.x
|
||||
|
||||
@ -10,7 +10,7 @@ To get started, load either the production or development version of Vue in your
|
||||
<!-- Production -->
|
||||
<script src="//cdn.jsdelivr.net/npm/vue@2/dist/vue.min.js"></script>
|
||||
|
||||
<!-- Development (debugging and Vue.js devtools support) -->
|
||||
<!-- Development -->
|
||||
<script src="//cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
|
||||
```
|
||||
|
||||
@ -20,118 +20,327 @@ To get started, load either the production or development version of Vue in your
|
||||
<!-- Production -->
|
||||
<script src="//cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js"></script>
|
||||
|
||||
<!-- Development (debugging and Vue.js devtools support) -->
|
||||
<!-- Development -->
|
||||
<script src="//cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script>
|
||||
```
|
||||
|
||||
## Basic rendering
|
||||
## Template syntax
|
||||
|
||||
Docsify will automatically render basic Vue content that does not require `data`, `methods`, or other instance features.
|
||||
Vue [template syntax](https://vuejs.org/v2/guide/syntax.html) is used to create dynamic content. With no additional configuration, this syntax offers several useful features like support for [JavaScript expressions](https://vuejs.org/v2/guide/syntax.html#Using-JavaScript-Expressions) and Vue [directives](https://vuejs.org/v2/guide/syntax.html#Directives) for loops and conditional rendering.
|
||||
|
||||
```markdown
|
||||
<!-- Hide in docsify, show elsewhere (e.g. GitHub) -->
|
||||
<p v-if="false">Text for GitHub</p>
|
||||
|
||||
<!-- Sequenced content (i.e. loop)-->
|
||||
<ul>
|
||||
<li v-for="i in 3">{{ i }}</li>
|
||||
<li v-for="i in 3">Item {{ i }}</li>
|
||||
</ul>
|
||||
|
||||
<!-- JavaScript expressions -->
|
||||
<p>2 + 2 = {{ 2 + 2 }}</p>
|
||||
```
|
||||
|
||||
The HTML above will render the following:
|
||||
<output data-lang="output">
|
||||
<p v-if="false">Text for GitHub</p>
|
||||
|
||||
<ul>
|
||||
<li v-for="i in 3">{{ i }}</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li v-for="i in 3">Item {{ i }}</li>
|
||||
</ul>
|
||||
|
||||
## Advanced usage
|
||||
<p>2 + 2 = {{ 2 + 2 }}</p>
|
||||
</output>
|
||||
|
||||
Vue components and templates that require `data`, `methods`, computed properties, lifecycle hooks, etc. require manually creating a new `Vue()` instance within a `<script>` tag in your markdown.
|
||||
[View output on GitHub](https://github.com/docsifyjs/docsify/blob/develop/docs/vue.md#template-syntax)
|
||||
|
||||
Vue content becomes more interesting when [data](#data), [computed properties](#computed-properties), [methods](#methods), and [lifecycle hooks](#lifecycle-hooks) are used. These options can be specified as [global options](#global-options) or within DOM [mounts](#mounts) and [components](#components).
|
||||
|
||||
### Data
|
||||
|
||||
```js
|
||||
{
|
||||
data() {
|
||||
return {
|
||||
message: 'Hello, World!'
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
```markdown
|
||||
<!-- Show message in docsify, show "{{ message }}" elsewhere (e.g. GitHub) -->
|
||||
{{ message }}
|
||||
|
||||
<!-- Show message in docsify, hide elsewhere (e.g. GitHub) -->
|
||||
<p v-text="message"></p>
|
||||
|
||||
<!-- Show message in docsify, show text elsewhere (e.g. GitHub) -->
|
||||
<p v-text="message">Text for GitHub</p>
|
||||
```
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<output data-lang="output">
|
||||
|
||||
{{ message }}
|
||||
|
||||
<p v-text="message"></p>
|
||||
<p v-text="message">Text for GitHub</p>
|
||||
</output>
|
||||
|
||||
[View output on GitHub](https://github.com/docsifyjs/docsify/blob/develop/docs/vue.md#data)
|
||||
|
||||
### Computed properties
|
||||
|
||||
```js
|
||||
{
|
||||
computed: {
|
||||
timeOfDay() {
|
||||
const date = new Date();
|
||||
const hours = date.getHours();
|
||||
|
||||
if (hours < 12) {
|
||||
return 'morning';
|
||||
}
|
||||
else if (hours < 18) {
|
||||
return 'afternoon';
|
||||
}
|
||||
else {
|
||||
return 'evening'
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
```markdown
|
||||
<div id="example-1">
|
||||
<p>{{ message }}</p>
|
||||
Good {{ timeOfDay }}!
|
||||
```
|
||||
|
||||
<button v-on:click="hello">Say Hello</button>
|
||||
<output data-lang="output">
|
||||
|
||||
<button v-on:click="counter -= 1">-</button>
|
||||
{{ counter }}
|
||||
<button v-on:click="counter += 1">+</button>
|
||||
Good {{ timeOfDay }}!
|
||||
|
||||
</output>
|
||||
|
||||
### Methods
|
||||
|
||||
```js
|
||||
{
|
||||
data() {
|
||||
return {
|
||||
message: 'Hello, World!'
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
hello() {
|
||||
alert(this.message);
|
||||
}
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
```markdown
|
||||
<button @click="hello">Say Hello</button>
|
||||
```
|
||||
|
||||
<output data-lang="output">
|
||||
<p><button @click="hello">Say Hello</button></p>
|
||||
</output>
|
||||
|
||||
### Lifecycle Hooks
|
||||
|
||||
```js
|
||||
{
|
||||
data() {
|
||||
return {
|
||||
images: null,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
fetch('https://api.domain.com/')
|
||||
.then(response => response.json())
|
||||
.then(data => (this.images = data))
|
||||
.catch(err => console.log(err));
|
||||
}
|
||||
}
|
||||
|
||||
// API response:
|
||||
// [
|
||||
// { title: 'Image 1', url: 'https://domain.com/1.jpg' },
|
||||
// { title: 'Image 2', url: 'https://domain.com/2.jpg' },
|
||||
// { title: 'Image 3', url: 'https://domain.com/3.jpg' },
|
||||
// ];
|
||||
```
|
||||
|
||||
```markdown
|
||||
<div style="display: flex;">
|
||||
<figure style="flex: 1;">
|
||||
<img v-for="image in images" :src="image.url" :title="image.title">
|
||||
<figcaption>{{ image.title }}</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
```
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
<output data-lang="output">
|
||||
<div style="display: flex;">
|
||||
<figure v-for="image in images" style="flex: 1; text-align: center;">
|
||||
<img :src="image.url">
|
||||
<figcaption>{{ image.title }}</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</output>
|
||||
|
||||
#### Vue 2.x
|
||||
## Global options
|
||||
|
||||
```markdown
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#example-1",
|
||||
data: function() {
|
||||
Use `vueGlobalOptions` to specify [Vue options](https://vuejs.org/v2/api/#Options-Data) for use with Vue content not explicitly mounted with [vueMounts](#mounts), [vueComponents](#components), or a [markdown script](#markdown-script). Changes to global `data` will persist and be reflected anywhere global references are used.
|
||||
|
||||
```js
|
||||
window.$docsify = {
|
||||
vueGlobalOptions: {
|
||||
data() {
|
||||
return {
|
||||
counter: 0,
|
||||
message: "Hello, World!"
|
||||
count: 0,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
hello: function() {
|
||||
alert(this.message);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
```markdown
|
||||
<p>
|
||||
<button @click="count -= 1">-</button>
|
||||
{{ count }}
|
||||
<button @click="count += 1">+</button>
|
||||
</p>
|
||||
```
|
||||
|
||||
<output data-lang="output">
|
||||
<p>
|
||||
<button @click="count -= 1">-</button>
|
||||
{{ count }}
|
||||
<button @click="count += 1">+</button>
|
||||
</p>
|
||||
</output>
|
||||
|
||||
Notice the behavior when multiple global counters are rendered:
|
||||
|
||||
<output data-lang="output">
|
||||
<p>
|
||||
<button @click="count -= 1">-</button>
|
||||
{{ count }}
|
||||
<button @click="count += 1">+</button>
|
||||
</p>
|
||||
</output>
|
||||
|
||||
Changes made to one counter affect the both counters. This is because both instances reference the same global `count` value. Now, navigate to a new page and return to this section to see how changes made to global data persist between page loads.
|
||||
|
||||
## Mounts
|
||||
|
||||
Use `vueMounts` to specify DOM elements to mount as [Vue instances](https://vuejs.org/v2/guide/instance.html) and their associated options. Mount elements are specified using a [CSS selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) as the key with an object containing Vue options as their value. Docsify will mount the first matching element in the main content area each time a new page is loaded. Mount element `data` is unique for each instance and will not persist as users navigate the site.
|
||||
|
||||
```js
|
||||
window.$docsify = {
|
||||
vueMounts: {
|
||||
'#counter': {
|
||||
data() {
|
||||
return {
|
||||
count: 0,
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
```markdown
|
||||
<div id="counter">
|
||||
<button @click="count -= 1">-</button>
|
||||
{{ count }}
|
||||
<button @click="count += 1">+</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
<output id="counter">
|
||||
<button @click="count -= 1">-</button>
|
||||
{{ count }}
|
||||
<button @click="count += 1">+</button>
|
||||
</output>
|
||||
|
||||
## Components
|
||||
|
||||
Use `vueComponents` to create and register global [Vue components](https://vuejs.org/v2/guide/components.html). Components are specified using the component name as the key with an object containing Vue options as the value. Component `data` is unique for each instance and will not persist as users navigate the site.
|
||||
|
||||
```js
|
||||
window.$docsify = {
|
||||
vueComponents: {
|
||||
'button-counter': {
|
||||
template: `
|
||||
<button @click="count += 1">
|
||||
You clicked me {{ count }} times
|
||||
</button>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
count: 0,
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
```markdown
|
||||
<button-counter></button-counter>
|
||||
<button-counter></button-counter>
|
||||
```
|
||||
|
||||
<output data-lang="output">
|
||||
<button-counter></button-counter>
|
||||
<button-counter></button-counter>
|
||||
</output>
|
||||
|
||||
## Markdown script
|
||||
|
||||
Vue content can mounted using a `<script>` tag in your markdown pages.
|
||||
|
||||
!> Only the first `<script>` tag in a markdown file is executed. If you wish to mount multiple Vue instances using a script tag, all instances must be mounted within the first script tag in your markdown.
|
||||
|
||||
```html
|
||||
<!-- Vue 2.x -->
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#example',
|
||||
// Options...
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
#### Vue 3.x
|
||||
|
||||
```markdown
|
||||
```html
|
||||
<!-- Vue 3.x -->
|
||||
<script>
|
||||
Vue.createApp({
|
||||
data: function() {
|
||||
return {
|
||||
counter: 0,
|
||||
message: "Hello, World!"
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
hello: function() {
|
||||
alert(this.message);
|
||||
}
|
||||
}
|
||||
}).mount("#example-1");
|
||||
// Options...
|
||||
}).mount('#example');
|
||||
</script>
|
||||
```
|
||||
|
||||
The HTML & JavaScript above will render the following:
|
||||
## Technical Notes
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
<div id="example-1">
|
||||
<p>{{ message }}</p>
|
||||
|
||||
<button v-on:click="hello">Say Hello</button>
|
||||
|
||||
<button v-on:click="counter -= 1">-</button>
|
||||
{{ counter }}
|
||||
<button v-on:click="counter += 1">+</button>
|
||||
</div>
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
!> Only the first `<script>` tag in a markdown file is executed. If you are working with multiple Vue components, all `Vue` instances must be created within this tag.
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#example-1",
|
||||
data: function() {
|
||||
return {
|
||||
counter: 0,
|
||||
message: "Hello, World!"
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
hello: function() {
|
||||
alert(this.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
- Docsify processes Vue content in the following order on each page load:
|
||||
1. Execute markdown `<script>`
|
||||
1. Register global `vueComponents`
|
||||
1. Mount `vueMounts`
|
||||
1. Auto-mount unmounted `vueComponents`
|
||||
1. Auto-mount unmounted Vue template syntax using `vueGlobalOptions`
|
||||
- When auto-mounting Vue content, docsify will mount each top-level element in your markdown that contains template syntax or a component. For example, in the following HTML the top-level `<p>`, `<my-component />`, and `<div>` elements will be mounted.
|
||||
```html
|
||||
<p>{{ foo }}</p>
|
||||
<my-component />
|
||||
<div>
|
||||
<span>{{ bar }}</span>
|
||||
<some-other-component />
|
||||
</div>
|
||||
```
|
||||
- Docsify will not mount an existing Vue instance or an element that contains an existing Vue instance.
|
||||
- Docsify will automatically destroy/unmount all Vue instances it creates before each page load.
|
||||
|
||||
@ -1,13 +1,9 @@
|
||||
const path = require('path');
|
||||
const { globals: serverGlobals } = require('./test/config/server.js');
|
||||
const { TEST_HOST } = require('./test/config/server.js');
|
||||
|
||||
const sharedConfig = {
|
||||
errorOnDeprecated: true,
|
||||
globals: {
|
||||
...serverGlobals, // BLANK_URL, DOCS_URL, LIB_URL, NODE_MODULES_URL, TEST_HOST
|
||||
DOCS_PATH: path.resolve(__dirname, 'docs'),
|
||||
LIB_PATH: path.resolve(__dirname, 'lib'),
|
||||
SRC_PATH: path.resolve(__dirname, 'src'),
|
||||
TEST_HOST,
|
||||
},
|
||||
globalSetup: './test/config/jest.setup.js',
|
||||
globalTeardown: './test/config/jest.teardown.js',
|
||||
@ -27,7 +23,7 @@ module.exports = {
|
||||
displayName: 'unit',
|
||||
setupFilesAfterEnv: ['<rootDir>/test/config/jest.setup-tests.js'],
|
||||
testMatch: ['<rootDir>/test/unit/*.test.js'],
|
||||
testURL: serverGlobals.BLANK_URL,
|
||||
testURL: `${TEST_HOST}/_blank.html`,
|
||||
},
|
||||
// Integration Tests (Jest)
|
||||
{
|
||||
@ -35,7 +31,7 @@ module.exports = {
|
||||
displayName: 'integration',
|
||||
setupFilesAfterEnv: ['<rootDir>/test/config/jest.setup-tests.js'],
|
||||
testMatch: ['<rootDir>/test/integration/*.test.js'],
|
||||
testURL: serverGlobals.BLANK_URL,
|
||||
testURL: `${TEST_HOST}/_blank.html`,
|
||||
},
|
||||
// E2E Tests (Jest + Playwright)
|
||||
{
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
"dev:ssr": "run-p serve:ssr watch:*",
|
||||
"lint": "eslint .",
|
||||
"fixlint": "eslint . --fix",
|
||||
"test": "jest --runInBand",
|
||||
"test": "jest",
|
||||
"test:e2e": "jest --selectProjects e2e",
|
||||
"test:integration": "jest --selectProjects integration",
|
||||
"test:unit": "jest --selectProjects unit",
|
||||
|
||||
@ -13,6 +13,8 @@ import { Compiler } from './compiler';
|
||||
import * as tpl from './tpl';
|
||||
import { prerenderEmbed } from './embed';
|
||||
|
||||
let vueGlobalData;
|
||||
|
||||
function executeScript() {
|
||||
const script = dom
|
||||
.findAll('.markdown-section>script')
|
||||
@ -41,58 +43,179 @@ function formatUpdated(html, updated, fn) {
|
||||
}
|
||||
|
||||
function renderMain(html) {
|
||||
const docsifyConfig = this.config;
|
||||
const markdownElm = dom.find('.markdown-section');
|
||||
const vueVersion =
|
||||
'Vue' in window &&
|
||||
window.Vue.version &&
|
||||
Number(window.Vue.version.charAt(0));
|
||||
|
||||
const isMountedVue = elm => {
|
||||
const isVue2 = Boolean(elm.__vue__ && elm.__vue__._isVue);
|
||||
const isVue3 = Boolean(elm._vnode && elm._vnode.__v_skip);
|
||||
|
||||
return isVue2 || isVue3;
|
||||
};
|
||||
|
||||
if (!html) {
|
||||
html = '<h1>404 - Not found</h1>';
|
||||
}
|
||||
|
||||
this._renderTo('.markdown-section', html);
|
||||
if ('Vue' in window) {
|
||||
const mountedElms = dom
|
||||
.findAll('.markdown-section > *')
|
||||
.filter(elm => isMountedVue(elm));
|
||||
|
||||
// Destroy/unmount existing Vue instances
|
||||
for (const mountedElm of mountedElms) {
|
||||
if (vueVersion === 2) {
|
||||
mountedElm.__vue__.$destroy();
|
||||
} else if (vueVersion === 3) {
|
||||
mountedElm.__vue_app__.unmount();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._renderTo(markdownElm, html);
|
||||
|
||||
// Render sidebar with the TOC
|
||||
!this.config.loadSidebar && this._renderSidebar();
|
||||
!docsifyConfig.loadSidebar && this._renderSidebar();
|
||||
|
||||
// Execute markdown <script>
|
||||
if (
|
||||
this.config.executeScript ||
|
||||
('Vue' in window && this.config.executeScript !== false)
|
||||
docsifyConfig.executeScript ||
|
||||
('Vue' in window && docsifyConfig.executeScript !== false)
|
||||
) {
|
||||
executeScript();
|
||||
}
|
||||
|
||||
// Handle Vue content not handled by markdown <script>
|
||||
// Handle Vue content not mounted by markdown <script>
|
||||
if ('Vue' in window) {
|
||||
const mainElm = document.querySelector('#main') || {};
|
||||
const childElms = mainElm.children || [];
|
||||
const vueVersion =
|
||||
window.Vue.version && Number(window.Vue.version.charAt(0));
|
||||
const vueMountData = [];
|
||||
const vueComponentNames = Object.keys(docsifyConfig.vueComponents || {});
|
||||
|
||||
for (let i = 0, len = childElms.length; i < len; i++) {
|
||||
const elm = childElms[i];
|
||||
const isValid = elm.tagName !== 'SCRIPT';
|
||||
// Register global vueComponents
|
||||
if (vueVersion === 2 && vueComponentNames.length) {
|
||||
vueComponentNames.forEach(name => {
|
||||
const isNotRegistered = !window.Vue.options.components[name];
|
||||
|
||||
if (!isValid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Vue 3
|
||||
if (vueVersion === 3) {
|
||||
const isAlreadyVue = Boolean(elm._vnode && elm._vnode.__v_skip);
|
||||
|
||||
if (!isAlreadyVue) {
|
||||
const app = window.Vue.createApp({});
|
||||
|
||||
app.mount(elm);
|
||||
if (isNotRegistered) {
|
||||
window.Vue.component(name, docsifyConfig.vueComponents[name]);
|
||||
}
|
||||
}
|
||||
// Vue 2
|
||||
else if (vueVersion === 2) {
|
||||
const isAlreadyVue = Boolean(elm.__vue__ && elm.__vue__._isVue);
|
||||
});
|
||||
}
|
||||
|
||||
if (!isAlreadyVue) {
|
||||
new window.Vue({
|
||||
mounted: function() {
|
||||
this.$destroy();
|
||||
},
|
||||
}).$mount(elm);
|
||||
// Store global data() return value as shared data object
|
||||
if (
|
||||
!vueGlobalData &&
|
||||
docsifyConfig.vueGlobalOptions &&
|
||||
typeof docsifyConfig.vueGlobalOptions.data === 'function'
|
||||
) {
|
||||
vueGlobalData = docsifyConfig.vueGlobalOptions.data();
|
||||
}
|
||||
|
||||
// vueMounts
|
||||
vueMountData.push(
|
||||
...Object.entries(docsifyConfig.vueMounts || {})
|
||||
.map(([cssSelector, vueConfig]) => [
|
||||
dom.find(markdownElm, cssSelector),
|
||||
vueConfig,
|
||||
])
|
||||
.filter(([elm, vueConfig]) => elm)
|
||||
);
|
||||
|
||||
// Template syntax, vueComponents, vueGlobalOptions
|
||||
if (docsifyConfig.vueGlobalOptions || vueComponentNames.length) {
|
||||
const reHasBraces = /{{2}[^{}]*}{2}/;
|
||||
// Matches Vue full and shorthand syntax as attributes in HTML tags.
|
||||
//
|
||||
// Full syntax examples:
|
||||
// v-foo, v-foo[bar], v-foo-bar, v-foo:bar-baz.prop
|
||||
//
|
||||
// Shorthand syntax examples:
|
||||
// @foo, @foo.bar, @foo.bar.baz, @[foo], :foo, :[foo]
|
||||
//
|
||||
// Markup examples:
|
||||
// <div v-html>{{ html }}</div>
|
||||
// <div v-text="msg"></div>
|
||||
// <div v-bind:text-content.prop="text">
|
||||
// <button v-on:click="doThis"></button>
|
||||
// <button v-on:click.once="doThis"></button>
|
||||
// <button v-on:[event]="doThis"></button>
|
||||
// <button @click.stop.prevent="doThis">
|
||||
// <a :href="url">
|
||||
// <a :[key]="url">
|
||||
const reHasDirective = /<[^>/]+\s([@:]|v-)[\w-:.[\]]+[=>\s]/;
|
||||
|
||||
vueMountData.push(
|
||||
...dom
|
||||
.findAll('.markdown-section > *')
|
||||
// Remove duplicates
|
||||
.filter(elm => !vueMountData.some(([e, c]) => e === elm))
|
||||
// Detect Vue content
|
||||
.filter(elm => {
|
||||
const isVueMount =
|
||||
// is a component
|
||||
elm.tagName.toLowerCase() in
|
||||
(docsifyConfig.vueComponents || {}) ||
|
||||
// has a component(s)
|
||||
elm.querySelector(vueComponentNames.join(',') || null) ||
|
||||
// has curly braces
|
||||
reHasBraces.test(elm.outerHTML) ||
|
||||
// has content directive
|
||||
reHasDirective.test(elm.outerHTML);
|
||||
|
||||
return isVueMount;
|
||||
})
|
||||
.map(elm => {
|
||||
// Clone global configuration
|
||||
const vueConfig = Object.assign(
|
||||
{},
|
||||
docsifyConfig.vueGlobalOptions || {}
|
||||
);
|
||||
|
||||
// Replace vueGlobalOptions data() return value with shared data object.
|
||||
// This provides a global store for all Vue instances that receive
|
||||
// vueGlobalOptions as their configuration.
|
||||
if (vueGlobalData) {
|
||||
vueConfig.data = function() {
|
||||
return vueGlobalData;
|
||||
};
|
||||
}
|
||||
|
||||
return [elm, vueConfig];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Mount
|
||||
for (const [mountElm, vueConfig] of vueMountData) {
|
||||
const isVueAttr = 'data-isvue';
|
||||
const isSkipElm =
|
||||
// Is an invalid tag
|
||||
mountElm.matches('pre, script') ||
|
||||
// Is a mounted instance
|
||||
isMountedVue(mountElm) ||
|
||||
// Has mounted instance(s)
|
||||
mountElm.querySelector(`[${isVueAttr}]`);
|
||||
|
||||
if (!isSkipElm) {
|
||||
mountElm.setAttribute(isVueAttr, '');
|
||||
|
||||
if (vueVersion === 2) {
|
||||
vueConfig.el = undefined;
|
||||
new window.Vue(vueConfig).$mount(mountElm);
|
||||
} else if (vueVersion === 3) {
|
||||
const app = window.Vue.createApp(vueConfig);
|
||||
|
||||
// Register global vueComponents
|
||||
vueComponentNames.forEach(name => {
|
||||
const config = docsifyConfig.vueComponents[name];
|
||||
|
||||
app.component(name, config);
|
||||
});
|
||||
|
||||
app.mount(mountElm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,28 +103,85 @@ body
|
||||
.markdown-section em
|
||||
color #7f8c8d
|
||||
|
||||
.markdown-section code
|
||||
.markdown-section code,
|
||||
.markdown-section pre,
|
||||
.markdown-section output::after
|
||||
font-family 'Roboto Mono', Monaco, courier, monospace
|
||||
|
||||
.markdown-section code,
|
||||
.markdown-section pre
|
||||
background-color #f8f8f8
|
||||
|
||||
.markdown-section pre,
|
||||
.markdown-section output
|
||||
margin 1.2em 0
|
||||
position relative
|
||||
|
||||
.markdown-section pre > code,
|
||||
.markdown-section output
|
||||
border-radius 2px
|
||||
display block
|
||||
|
||||
.markdown-section pre > code,
|
||||
.markdown-section output::after
|
||||
-moz-osx-font-smoothing initial
|
||||
-webkit-font-smoothing initial
|
||||
|
||||
.markdown-section code
|
||||
border-radius 2px
|
||||
color #e96900
|
||||
font-family 'Roboto Mono', Monaco, courier, monospace
|
||||
font-size 0.8rem
|
||||
margin 0 2px
|
||||
padding 3px 5px
|
||||
white-space pre-wrap
|
||||
|
||||
.markdown-section pre
|
||||
-moz-osx-font-smoothing initial
|
||||
-webkit-font-smoothing initial
|
||||
background-color #f8f8f8
|
||||
font-family 'Roboto Mono', Monaco, courier, monospace
|
||||
line-height 1.5rem
|
||||
margin 1.2em 0
|
||||
overflow auto
|
||||
padding 0 1.4rem
|
||||
position relative
|
||||
line-height 1.5rem
|
||||
overflow auto
|
||||
word-wrap normal
|
||||
|
||||
.markdown-section pre > code
|
||||
color #525252
|
||||
font-size 0.8rem
|
||||
padding 2.2em 5px
|
||||
line-height inherit
|
||||
margin 0 2px
|
||||
max-width inherit
|
||||
overflow inherit
|
||||
white-space inherit
|
||||
|
||||
.markdown-section output
|
||||
padding: 1.7rem 1.4rem
|
||||
border 1px dotted #ccc
|
||||
|
||||
.markdown-section output > :first-child
|
||||
margin-top: 0;
|
||||
|
||||
.markdown-section output > :last-child
|
||||
margin-bottom: 0;
|
||||
|
||||
.markdown-section code::after, .markdown-section code::before,
|
||||
.markdown-section output::after, .markdown-section output::before
|
||||
letter-spacing 0.05rem
|
||||
|
||||
.markdown-section pre::after,
|
||||
.markdown-section output::after
|
||||
color #ccc
|
||||
font-size 0.6rem
|
||||
font-weight 600
|
||||
height 15px
|
||||
line-height 15px
|
||||
padding 5px 10px 0
|
||||
position absolute
|
||||
right 0
|
||||
text-align right
|
||||
top 0
|
||||
|
||||
.markdown-section pre::after
|
||||
.markdown-section output::after
|
||||
content attr(data-lang)
|
||||
|
||||
/* code highlight */
|
||||
.token.comment, .token.prolog, .token.doctype, .token.cdata
|
||||
color #8e908c
|
||||
@ -187,41 +244,9 @@ body
|
||||
.token.entity
|
||||
cursor help
|
||||
|
||||
.markdown-section pre > code
|
||||
-moz-osx-font-smoothing initial
|
||||
-webkit-font-smoothing initial
|
||||
background-color #f8f8f8
|
||||
border-radius 2px
|
||||
color #525252
|
||||
display block
|
||||
font-family 'Roboto Mono', Monaco, courier, monospace
|
||||
font-size 0.8rem
|
||||
line-height inherit
|
||||
margin 0 2px
|
||||
max-width inherit
|
||||
overflow inherit
|
||||
padding 2.2em 5px
|
||||
white-space inherit
|
||||
|
||||
.markdown-section code::after, .markdown-section code::before
|
||||
letter-spacing 0.05rem
|
||||
|
||||
code .token
|
||||
-moz-osx-font-smoothing initial
|
||||
-webkit-font-smoothing initial
|
||||
min-height 1.5rem
|
||||
position: relative
|
||||
left: auto
|
||||
|
||||
pre::after
|
||||
color #ccc
|
||||
content attr(data-lang)
|
||||
font-size 0.6rem
|
||||
font-weight 600
|
||||
height 15px
|
||||
line-height 15px
|
||||
padding 5px 10px 0
|
||||
position absolute
|
||||
right 0
|
||||
text-align right
|
||||
top 0
|
||||
|
||||
@ -5,8 +5,6 @@ module.exports = {
|
||||
'jest/globals': true,
|
||||
},
|
||||
extends: ['plugin:jest/recommended', 'plugin:jest/style'],
|
||||
globals: {
|
||||
...jestConfig.globals,
|
||||
},
|
||||
globals: jestConfig.globals,
|
||||
plugins: ['jest'],
|
||||
};
|
||||
|
||||
@ -14,18 +14,6 @@
|
||||
|
||||
## Global Variables
|
||||
|
||||
### File paths
|
||||
|
||||
- `DOCS_PATH`: File path to `/docs` directory
|
||||
- `LIB_PATH`: File path to `/lib` directory
|
||||
- `SRC_PATH`: File path to `/src` directory
|
||||
|
||||
### URLs
|
||||
|
||||
- `BLANK_URL`: Test server route to virtual `_blank.html` file
|
||||
- `DOCS_URL`: Test server route to `/docs` directory
|
||||
- `LIB_URL`: Test server route to `/lib` directory
|
||||
- `NODE_MODULES_URL`: Test server route to `/node_modules` directory
|
||||
- `TEST_HOST`: Test server ip:port
|
||||
|
||||
## CLI commands
|
||||
|
||||
@ -50,5 +50,5 @@ beforeEach(async () => {
|
||||
// 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(BLANK_URL);
|
||||
await page.goto(`${TEST_HOST}/_blank.html`);
|
||||
});
|
||||
|
||||
@ -1,42 +1,83 @@
|
||||
import mock from 'xhr-mock';
|
||||
|
||||
const sideEffects = {
|
||||
document: {
|
||||
addEventListener: {
|
||||
fn: document.addEventListener,
|
||||
refs: [],
|
||||
},
|
||||
keys: Object.keys(document),
|
||||
},
|
||||
window: {
|
||||
addEventListener: {
|
||||
fn: window.addEventListener,
|
||||
refs: [],
|
||||
},
|
||||
keys: Object.keys(window),
|
||||
},
|
||||
};
|
||||
|
||||
// Lifecycle Hooks
|
||||
// -----------------------------------------------------------------------------
|
||||
// Soft-reset jsdom. This clears the DOM and removes all attribute from the
|
||||
// root element, however it does not undo changes made to jsdom globals like
|
||||
// the window or document object. Tests requiring a full jsdom reset should be
|
||||
// stored in separate files, as this is the only way (?) to do a complete
|
||||
// reset of JSDOM with Jest.
|
||||
beforeAll(async () => {
|
||||
// Spy addEventListener
|
||||
['document', 'window'].forEach(obj => {
|
||||
const fn = sideEffects[obj].addEventListener.fn;
|
||||
const refs = sideEffects[obj].addEventListener.refs;
|
||||
|
||||
function addEventListenerSpy(type, listener, options) {
|
||||
// Store listener reference so it can be removed during reset
|
||||
refs.push({ type, listener, options });
|
||||
// Call original window.addEventListener
|
||||
fn(type, listener, options);
|
||||
}
|
||||
|
||||
// Add to default key array to prevent removal during reset
|
||||
sideEffects[obj].keys.push('addEventListener');
|
||||
|
||||
// Replace addEventListener with mock
|
||||
global[obj].addEventListener = addEventListenerSpy;
|
||||
});
|
||||
});
|
||||
|
||||
// Reset JSDOM. This attempts to remove side effects from tests, however it does
|
||||
// not reset all changes made to globals like the window and document
|
||||
// objects. Tests requiring a full JSDOM reset should be stored in separate
|
||||
// files, which is only way to do a complete JSDOM reset with Jest.
|
||||
beforeEach(async () => {
|
||||
const rootElm = document.documentElement;
|
||||
|
||||
// Remove elements (faster the setting innerHTML)
|
||||
// Remove attributes on root element
|
||||
[...rootElm.attributes].forEach(attr => rootElm.removeAttribute(attr.name));
|
||||
|
||||
// Remove elements (faster than setting innerHTML)
|
||||
while (rootElm.firstChild) {
|
||||
rootElm.removeChild(rootElm.firstChild);
|
||||
}
|
||||
|
||||
// Remove docsify side-effects
|
||||
[
|
||||
'__current_docsify_compiler__',
|
||||
'_paq',
|
||||
'$docsify',
|
||||
'Docsify',
|
||||
'DocsifyCompiler',
|
||||
'ga',
|
||||
'gaData',
|
||||
'gaGlobal',
|
||||
'gaplugins',
|
||||
'gitter',
|
||||
'google_tag_data',
|
||||
'marked',
|
||||
'Prism',
|
||||
].forEach(prop => {
|
||||
if (global[prop]) {
|
||||
delete global[prop];
|
||||
// Remove global listeners and keys
|
||||
['document', 'window'].forEach(obj => {
|
||||
const refs = sideEffects[obj].addEventListener.refs;
|
||||
|
||||
// Listeners
|
||||
while (refs.length) {
|
||||
const { type, listener, options } = refs.pop();
|
||||
global[obj].removeEventListener(type, listener, options);
|
||||
}
|
||||
|
||||
// Keys
|
||||
Object.keys(global[obj])
|
||||
.filter(key => !sideEffects[obj].keys.includes(key))
|
||||
.forEach(key => {
|
||||
delete global[obj][key];
|
||||
});
|
||||
});
|
||||
|
||||
// Remove attributes
|
||||
[...rootElm.attributes].forEach(attr => rootElm.removeAttribute(attr.name));
|
||||
|
||||
// Restore base elements
|
||||
rootElm.innerHTML = '<html><head></head><body></body></html>';
|
||||
rootElm.innerHTML = '<head></head><body></body>';
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// Restore the global XMLHttpRequest object to its original state
|
||||
mock.teardown();
|
||||
});
|
||||
|
||||
@ -59,7 +59,6 @@ function startServer(options = {}, cb = Function.prototype) {
|
||||
},
|
||||
},
|
||||
},
|
||||
startPath: '/docs',
|
||||
ui: false,
|
||||
};
|
||||
|
||||
@ -96,6 +95,7 @@ if (hasStartArg) {
|
||||
open: true,
|
||||
port: serverConfig.port + 1,
|
||||
directory: true,
|
||||
startPath: '/docs',
|
||||
});
|
||||
}
|
||||
// Display friendly message about manually starting a server instance
|
||||
@ -104,22 +104,8 @@ else if (require.main === module) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
globals: {
|
||||
get BLANK_URL() {
|
||||
return `${this.TEST_HOST}/_blank.html`;
|
||||
},
|
||||
get DOCS_URL() {
|
||||
return `${this.TEST_HOST}/docs`;
|
||||
},
|
||||
get LIB_URL() {
|
||||
return `${this.TEST_HOST}/lib`;
|
||||
},
|
||||
get NODE_MODULES_URL() {
|
||||
return `${this.TEST_HOST}/node_modules`;
|
||||
},
|
||||
TEST_HOST: `http://${serverConfig.host}:${serverConfig.port}`,
|
||||
},
|
||||
start: startServer,
|
||||
startAsync: startServerAsync,
|
||||
stop: stopServer,
|
||||
TEST_HOST: `http://${serverConfig.host}:${serverConfig.port}`,
|
||||
};
|
||||
|
||||
@ -92,14 +92,14 @@ describe(`Example Tests`, function() {
|
||||
test('manual docsify site using playwright methods', async () => {
|
||||
// Goto URL
|
||||
// https://playwright.dev/#path=docs%2Fapi.md&q=pagegotourl-options
|
||||
await page.goto(BLANK_URL);
|
||||
await page.goto(`${TEST_HOST}/_blank.html`);
|
||||
|
||||
// Set docsify configuration
|
||||
// https://playwright.dev/#path=docs%2Fapi.md&q=pageevaluatepagefunction-arg
|
||||
await page.evaluate(() => {
|
||||
window.$docsify = {
|
||||
el: '#app',
|
||||
basePath: '/docs',
|
||||
basePath: '/docs/',
|
||||
themeColor: 'red',
|
||||
};
|
||||
});
|
||||
@ -112,11 +112,11 @@ describe(`Example Tests`, function() {
|
||||
|
||||
// Inject docsify theme (vue.css)
|
||||
// https://playwright.dev/#path=docs%2Fapi.md&q=pageaddstyletagoptions
|
||||
await page.addStyleTag({ url: `${LIB_URL}/themes/vue.css` });
|
||||
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_URL}/docsify.js` });
|
||||
await page.addScriptTag({ url: '/lib/docsify.js' });
|
||||
|
||||
// Wait for docsify to initialize
|
||||
// https://playwright.dev/#path=docs%2Fapi.md&q=pagewaitforselectorselector-options
|
||||
@ -182,12 +182,12 @@ describe(`Example Tests`, function() {
|
||||
`,
|
||||
},
|
||||
routes: {
|
||||
'/test.md': `
|
||||
'test.md': `
|
||||
# Test Page
|
||||
|
||||
This is a custom route.
|
||||
`,
|
||||
'/data-test-scripturls.js': `
|
||||
'data-test-scripturls.js': `
|
||||
document.body.setAttribute('data-test-scripturls', 'pass');
|
||||
`,
|
||||
},
|
||||
@ -196,7 +196,7 @@ describe(`Example Tests`, function() {
|
||||
`,
|
||||
scriptURLs: [
|
||||
// docsifyInit() route
|
||||
'/data-test-scripturls.js',
|
||||
'data-test-scripturls.js',
|
||||
// Server route
|
||||
'/lib/plugins/search.min.js',
|
||||
],
|
||||
|
||||
@ -1,141 +1,206 @@
|
||||
const stripIndent = require('common-tags/lib/stripIndent');
|
||||
const docsifyInit = require('../helpers/docsify-init');
|
||||
|
||||
describe('Vue.js Rendering', function() {
|
||||
const vueURLs = [
|
||||
`${NODE_MODULES_URL}/vue2/dist/vue.js`,
|
||||
`${NODE_MODULES_URL}/vue3/dist/vue.global.js`,
|
||||
];
|
||||
const vueURLs = [
|
||||
'/node_modules/vue2/dist/vue.js',
|
||||
'/node_modules/vue3/dist/vue.global.js',
|
||||
];
|
||||
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
test(`ignores Vue content when window.Vue is not present`, async () => {
|
||||
await docsifyInit({
|
||||
markdown: {
|
||||
homepage: `
|
||||
<div id="test">test<span v-for="i in 5">{{ i }}</span></div>
|
||||
`,
|
||||
},
|
||||
});
|
||||
|
||||
await page.evaluate(() => {
|
||||
return 'Vue' in window === false;
|
||||
});
|
||||
await expect(page).toEqualText('#test', 'test{{ i }}');
|
||||
});
|
||||
|
||||
describe('Basic rendering', function() {
|
||||
for (const vueURL of vueURLs) {
|
||||
const vueVersion = vueURL.match(/vue(\d+)/)[1]; // vue2|vue3
|
||||
|
||||
for (const executeScript of ['unspecified', true, false]) {
|
||||
test(`handles Vue v${vueVersion}.x basic rendering when executeScript is ${executeScript}`, async () => {
|
||||
const docsifyInitConfig = {
|
||||
markdown: {
|
||||
homepage: `
|
||||
<div id="test">test<span v-for="i in 5">{{ i }}</span></div>
|
||||
`,
|
||||
describe('Vue.js Compatibility', function() {
|
||||
function getSharedConfig() {
|
||||
const config = {
|
||||
config: {
|
||||
vueComponents: {
|
||||
'button-counter': {
|
||||
template: `
|
||||
<button @click="counter++">{{ counter }}</button>
|
||||
`,
|
||||
data: function() {
|
||||
return {
|
||||
counter: 0,
|
||||
};
|
||||
},
|
||||
scriptURLs: vueURL,
|
||||
};
|
||||
|
||||
if (executeScript !== 'unspecified') {
|
||||
docsifyInitConfig.config = {
|
||||
executeScript,
|
||||
},
|
||||
},
|
||||
vueGlobalOptions: {
|
||||
data: function() {
|
||||
return {
|
||||
counter: 0,
|
||||
msg: 'vueglobaloptions',
|
||||
};
|
||||
}
|
||||
|
||||
await docsifyInit(docsifyInitConfig);
|
||||
|
||||
await expect(page).toEqualText('#test', 'test12345');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
describe('Advanced usage', function() {
|
||||
const testData = {
|
||||
vue2: {
|
||||
markdown: `
|
||||
<div id="test">
|
||||
<button v-on:click="counter += 1">+</button>
|
||||
<span>{{ counter }}<span>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#test',
|
||||
data() {
|
||||
return {
|
||||
counter: 0
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
`,
|
||||
},
|
||||
},
|
||||
vueMounts: {
|
||||
'#vuemounts': {
|
||||
data: function() {
|
||||
return {
|
||||
counter: 0,
|
||||
msg: 'vuemounts',
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
vue3: {
|
||||
markdown: `
|
||||
<div id="test">
|
||||
<button v-on:click="counter += 1">+</button>
|
||||
markdown: {
|
||||
homepage: stripIndent`
|
||||
<div id="vuefor"><span v-for="i in 5">{{ i }}</span></div>
|
||||
|
||||
<button-counter id="vuecomponent">---</button-counter>
|
||||
|
||||
<div id="vueglobaloptions">
|
||||
<p v-text="msg">---</p>
|
||||
<button @click="counter += 1">+</button>
|
||||
<span>{{ counter }}<span>
|
||||
</div>
|
||||
|
||||
<div id="vuemounts">
|
||||
<p v-text="msg">---</p>
|
||||
<button @click="counter += 1">+</button>
|
||||
<span>{{ counter }}<span>
|
||||
</div>
|
||||
|
||||
<div id="vuescript">
|
||||
<p v-text="msg">---</p>
|
||||
<button @click="counter += 1">+</button>
|
||||
<span>{{ counter }}<span>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
Vue.createApp({
|
||||
const vueConfig = {
|
||||
data() {
|
||||
return {
|
||||
counter: 0
|
||||
counter: 0,
|
||||
msg: 'vuescript'
|
||||
}
|
||||
},
|
||||
}).mount('#test');
|
||||
}
|
||||
const vueMountElm = '#vuescript';
|
||||
const vueVersion = Number(window.Vue.version.charAt(0));
|
||||
|
||||
if (vueVersion === 2) {
|
||||
new Vue(vueConfig).$mount(vueMountElm);
|
||||
}
|
||||
else if (vueVersion === 3) {
|
||||
Vue.createApp(vueConfig).mount(vueMountElm);
|
||||
}
|
||||
</script>
|
||||
`,
|
||||
},
|
||||
};
|
||||
|
||||
for (const vueURL of vueURLs) {
|
||||
const vueVersion = vueURL.match(/vue(\d+)/)[1]; // vue2|vue3
|
||||
const vueData = testData[`vue${vueVersion}`];
|
||||
return config;
|
||||
}
|
||||
|
||||
for (const executeScript of ['unspecified', true]) {
|
||||
test(`handles Vue v${vueVersion}.x advanced usage when executeScript is ${executeScript}`, async () => {
|
||||
const docsifyInitConfig = {
|
||||
markdown: {
|
||||
homepage: vueData.markdown,
|
||||
},
|
||||
scriptURLs: vueURL,
|
||||
};
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
for (const vueURL of vueURLs) {
|
||||
const vueVersion = Number(vueURL.match(/vue(\d+)/)[1]); // 2|3
|
||||
|
||||
if (executeScript !== 'unspecified') {
|
||||
docsifyInitConfig.config = {
|
||||
executeScript,
|
||||
};
|
||||
}
|
||||
describe(`Vue v${vueVersion}`, function() {
|
||||
for (const executeScript of [true, undefined]) {
|
||||
test(`renders content when executeScript is ${executeScript}`, async () => {
|
||||
const docsifyInitConfig = getSharedConfig();
|
||||
|
||||
docsifyInitConfig.config.executeScript = executeScript;
|
||||
docsifyInitConfig.scriptURLs = vueURL;
|
||||
|
||||
await docsifyInit(docsifyInitConfig);
|
||||
|
||||
await expect(page).toEqualText('#test span', '0');
|
||||
await page.click('#test button');
|
||||
await expect(page).toEqualText('#test span', '1');
|
||||
// Static
|
||||
await expect(page).toEqualText('#vuefor', '12345');
|
||||
await expect(page).toEqualText('#vuecomponent', '0');
|
||||
await expect(page).toEqualText(
|
||||
'#vueglobaloptions p',
|
||||
'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');
|
||||
|
||||
// Reactive
|
||||
await page.click('#vuecomponent');
|
||||
await expect(page).toEqualText('#vuecomponent', '1');
|
||||
await page.click('#vueglobaloptions button');
|
||||
await expect(page).toEqualText('#vueglobaloptions span', '1');
|
||||
await page.click('#vuemounts button');
|
||||
await expect(page).toEqualText('#vuemounts span', '1');
|
||||
await page.click('#vuescript button');
|
||||
await expect(page).toEqualText('#vuescript span', '1');
|
||||
});
|
||||
}
|
||||
|
||||
test(`handles Vue v${vueVersion}.x advanced usage when executeScript is false`, async () => {
|
||||
const docsifyInitConfig = {
|
||||
config: {
|
||||
executeScript: false,
|
||||
},
|
||||
markdown: {
|
||||
homepage: vueData.markdown,
|
||||
},
|
||||
scriptURLs: vueURL,
|
||||
};
|
||||
test(`ignores content when Vue is not present`, async () => {
|
||||
const docsifyInitConfig = getSharedConfig();
|
||||
|
||||
await docsifyInit(docsifyInitConfig);
|
||||
|
||||
const textContent = await page.textContent('#test span');
|
||||
expect(textContent).toBe('');
|
||||
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', '---');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test(`ignores content when vueComponents, vueMounts, and vueGlobalOptions are undefined`, async () => {
|
||||
const docsifyInitConfig = getSharedConfig();
|
||||
|
||||
docsifyInitConfig.config.vueComponents = undefined;
|
||||
docsifyInitConfig.config.vueGlobalOptions = undefined;
|
||||
docsifyInitConfig.config.vueMounts = undefined;
|
||||
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');
|
||||
});
|
||||
|
||||
test(`ignores content when vueGlobalOptions is undefined`, async () => {
|
||||
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');
|
||||
});
|
||||
|
||||
test(`ignores content when vueMounts is undefined`, async () => {
|
||||
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',
|
||||
'vueglobaloptions'
|
||||
);
|
||||
await expect(page).toEqualText('#vuemounts p', 'vueglobaloptions');
|
||||
await expect(page).toEqualText('#vuescript p', 'vuescript');
|
||||
});
|
||||
|
||||
test(`ignores <script> when executeScript is false`, async () => {
|
||||
const docsifyInitConfig = getSharedConfig();
|
||||
|
||||
docsifyInitConfig.config.executeScript = false;
|
||||
docsifyInitConfig.scriptURLs = vueURL;
|
||||
|
||||
await docsifyInit(docsifyInitConfig);
|
||||
await expect(page).toEqualText('#vuescript p', 'vueglobaloptions');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -6,8 +6,8 @@ const axios = require('axios');
|
||||
const prettier = require('prettier');
|
||||
const stripIndent = require('common-tags/lib/stripIndent');
|
||||
|
||||
const docsifyPATH = `${LIB_PATH}/docsify.js`; // JSDOM
|
||||
const docsifyURL = `${LIB_URL}/docsify.js`; // Playwright
|
||||
const docsifyPATH = '../../lib/docsify.js'; // JSDOM
|
||||
const docsifyURL = '/lib/docsify.js'; // Playwright
|
||||
const isJSDOM = 'window' in global;
|
||||
const isPlaywright = 'page' in global;
|
||||
|
||||
@ -30,8 +30,9 @@ const isPlaywright = 'page' in global;
|
||||
* @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 {Boolean|Object} [options._logHTML] Logs HTML to console after initialization
|
||||
* @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 = {}) {
|
||||
@ -63,7 +64,7 @@ async function docsifyInit(options = {}) {
|
||||
style: '',
|
||||
styleURLs: [],
|
||||
testURL: `${TEST_HOST}/docsify-init.html`,
|
||||
waitForSelector: '#main',
|
||||
waitForSelector: '#main > *',
|
||||
};
|
||||
const settings = {
|
||||
...defaults,
|
||||
@ -77,15 +78,29 @@ async function docsifyInit(options = {}) {
|
||||
loadSidebar: Boolean(settings.markdown.sidebar),
|
||||
};
|
||||
|
||||
const updateBasePath = config => {
|
||||
if (config.basePath) {
|
||||
config.basePath = new URL(config.basePath, TEST_HOST).href;
|
||||
}
|
||||
};
|
||||
|
||||
// Config as function
|
||||
if (typeof options.config === 'function') {
|
||||
return function(vm) {
|
||||
return { ...sharedConfig, ...options.config(vm) };
|
||||
const config = { ...sharedConfig, ...options.config(vm) };
|
||||
|
||||
updateBasePath(config);
|
||||
|
||||
return config;
|
||||
};
|
||||
}
|
||||
// Config as object
|
||||
else {
|
||||
return { ...sharedConfig, ...options.config };
|
||||
const config = { ...sharedConfig, ...options.config };
|
||||
|
||||
updateBasePath(config);
|
||||
|
||||
return config;
|
||||
}
|
||||
},
|
||||
get markdown() {
|
||||
@ -101,10 +116,10 @@ async function docsifyInit(options = {}) {
|
||||
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(
|
||||
@ -116,7 +131,7 @@ async function docsifyInit(options = {}) {
|
||||
.filter(([url, responseText]) => url && responseText)
|
||||
.map(([url, responseText]) => [
|
||||
// Convert relative to absolute URL
|
||||
new URL(url, TEST_HOST).href,
|
||||
new URL(url, settings.config.basePath || TEST_HOST).href,
|
||||
// Strip indentation from responseText
|
||||
stripIndent`${responseText}`,
|
||||
])
|
||||
@ -197,9 +212,21 @@ async function docsifyInit(options = {}) {
|
||||
if (isJSDOM) {
|
||||
window.$docsify = settings.config;
|
||||
} else if (isPlaywright) {
|
||||
// Convert config functions to strings
|
||||
const configString = JSON.stringify(settings.config, (key, val) =>
|
||||
typeof val === 'function' ? `__FN__${val.toString()}` : val
|
||||
);
|
||||
|
||||
await page.evaluate(config => {
|
||||
window.$docsify = config;
|
||||
}, settings.config);
|
||||
// Restore config functions from strings
|
||||
const configObj = JSON.parse(config, (key, val) =>
|
||||
/^__FN__/.test(val)
|
||||
? new Function(`return ${val.split('__FN__')[1]}`)()
|
||||
: val
|
||||
);
|
||||
|
||||
window.$docsify = configObj;
|
||||
}, configString);
|
||||
}
|
||||
|
||||
// Style URLs
|
||||
@ -285,16 +312,42 @@ async function docsifyInit(options = {}) {
|
||||
|
||||
// Log HTML to console
|
||||
if (settings._logHTML) {
|
||||
const html = isJSDOM
|
||||
? document.documentElement.innerHTML
|
||||
: await page.content();
|
||||
const output =
|
||||
settings._logHTML.format === false
|
||||
? html
|
||||
: prettier.format(html, { parser: 'html' });
|
||||
const selector =
|
||||
typeof settings._logHTML === 'string'
|
||||
? settings._logHTML
|
||||
: settings._logHTML.selector;
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(output);
|
||||
let htmlArr = [];
|
||||
|
||||
if (selector) {
|
||||
if (isJSDOM) {
|
||||
htmlArr = [...document.querySelectorAll(selector)].map(
|
||||
elm => elm.outerHTML
|
||||
);
|
||||
} else {
|
||||
htmlArr = await page.$$eval(selector, elms =>
|
||||
elms.map(e => e.outerHTML)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
htmlArr = [
|
||||
isJSDOM ? document.documentElement.outerHTML : await page.content(),
|
||||
];
|
||||
}
|
||||
|
||||
if (htmlArr.length) {
|
||||
htmlArr.forEach(html => {
|
||||
if (settings._logHTML.format !== false) {
|
||||
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}'`);
|
||||
}
|
||||
}
|
||||
|
||||
// Debug
|
||||
|
||||
@ -37,9 +37,11 @@ export function waitForFunction(fn, arg, options = {}) {
|
||||
timeElapsed += settings.delay;
|
||||
|
||||
if (timeElapsed >= settings.timeout) {
|
||||
reject(
|
||||
`waitForFunction() did not return a truthy value\n\n${fn.toString()}\n\n`
|
||||
);
|
||||
const msg = `waitForFunction did not return a truthy value (${
|
||||
settings.timeout
|
||||
} ms): ${fn.toString()}\n`;
|
||||
|
||||
reject(msg);
|
||||
}
|
||||
}, settings.delay);
|
||||
});
|
||||
@ -71,9 +73,9 @@ export function waitForSelector(cssSelector, options = {}) {
|
||||
timeElapsed += settings.delay;
|
||||
|
||||
if (timeElapsed >= settings.timeout) {
|
||||
reject(
|
||||
`waitForSelector() was unable to find CSS selector ${cssSelector}`
|
||||
);
|
||||
const msg = `waitForSelector did not match CSS selector '${cssSelector}' (${settings.timeout} ms)`;
|
||||
|
||||
reject(msg);
|
||||
}
|
||||
}, settings.delay);
|
||||
});
|
||||
@ -94,20 +96,31 @@ export function waitForText(cssSelector, text, options = {}) {
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let timeElapsed = 0;
|
||||
|
||||
waitForSelector(cssSelector, settings)
|
||||
.then(elm => {
|
||||
const isMatch = elm.textContent.includes(text);
|
||||
const int = setInterval(() => {
|
||||
const isMatch = elm.textContent.includes(text);
|
||||
|
||||
if (isMatch) {
|
||||
resolve(elm);
|
||||
} else {
|
||||
reject(
|
||||
`waitForText() did not find "${text}" in CSS selector ${cssSelector}`
|
||||
);
|
||||
}
|
||||
if (isMatch) {
|
||||
clearInterval(int);
|
||||
resolve(true);
|
||||
}
|
||||
|
||||
timeElapsed += settings.delay;
|
||||
|
||||
if (timeElapsed >= settings.timeout) {
|
||||
const msg = `waitForText did not find '${text}' in CSS selector '${cssSelector}' (${settings.timeout} ms): '${elm.textContent}'`;
|
||||
|
||||
reject(msg);
|
||||
}
|
||||
}, settings.delay);
|
||||
})
|
||||
.catch(() => {
|
||||
reject(`waitForText() was unable to find CSS selector ${cssSelector}`);
|
||||
const msg = `waitForText did not match CSS selector '${cssSelector}' (${settings.timeout} ms)`;
|
||||
|
||||
reject(msg);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
const docsifyInit = require('../helpers/docsify-init');
|
||||
|
||||
// Suite
|
||||
@ -13,14 +11,13 @@ describe('Docs Site', function() {
|
||||
const mathSpy = jest.spyOn(Math, 'random').mockReturnValue(0.5);
|
||||
|
||||
await docsifyInit({
|
||||
config: {
|
||||
coverpage: 'docs/_coverpage.md',
|
||||
},
|
||||
markdown: {
|
||||
homepage: '# Hello World',
|
||||
coverpage: await fs.readFile(
|
||||
path.resolve(DOCS_PATH, '_coverpage.md'),
|
||||
'utf8'
|
||||
),
|
||||
},
|
||||
// _logHTML: true,
|
||||
waitForSelector: '.cover-main > *',
|
||||
});
|
||||
|
||||
const coverpageElm = document.querySelector('section.cover');
|
||||
@ -33,14 +30,13 @@ describe('Docs Site', function() {
|
||||
|
||||
test('sidebar renders and is unchanged', async () => {
|
||||
await docsifyInit({
|
||||
config: {
|
||||
loadSidebar: 'docs/_sidebar.md',
|
||||
},
|
||||
markdown: {
|
||||
homepage: '# Hello World',
|
||||
sidebar: await fs.readFile(
|
||||
path.resolve(DOCS_PATH, '_sidebar.md'),
|
||||
'utf8'
|
||||
),
|
||||
},
|
||||
// _logHTML: true,
|
||||
waitForSelector: '.sidebar-nav > ul',
|
||||
});
|
||||
|
||||
const sidebarElm = document.querySelector('.sidebar');
|
||||
@ -52,14 +48,13 @@ describe('Docs Site', function() {
|
||||
|
||||
test('navbar renders and is unchanged', async () => {
|
||||
await docsifyInit({
|
||||
config: {
|
||||
loadNavbar: 'docs/_navbar.md',
|
||||
},
|
||||
markdown: {
|
||||
homepage: '# Hello World',
|
||||
navbar: await fs.readFile(
|
||||
path.resolve(DOCS_PATH, '_navbar.md'),
|
||||
'utf8'
|
||||
),
|
||||
},
|
||||
// _logHTML: true,
|
||||
waitForSelector: '.app-nav > ul',
|
||||
});
|
||||
|
||||
const navbarElm = document.querySelector('nav.app-nav');
|
||||
|
||||
@ -10,7 +10,7 @@ describe('Example Tests', function() {
|
||||
test('Docsify /docs/ site using docsifyInit()', async () => {
|
||||
await docsifyInit({
|
||||
config: {
|
||||
basePath: `${TEST_HOST}/docs`,
|
||||
basePath: '/docs/',
|
||||
},
|
||||
// _logHTML: true,
|
||||
});
|
||||
@ -51,12 +51,12 @@ describe('Example Tests', function() {
|
||||
`,
|
||||
},
|
||||
routes: {
|
||||
'/test.md': `
|
||||
'test.md': `
|
||||
# Test Page
|
||||
|
||||
This is a custom route.
|
||||
`,
|
||||
'/data-test-scripturls.js': `
|
||||
'data-test-scripturls.js': `
|
||||
document.body.setAttribute('data-test-scripturls', 'pass');
|
||||
`,
|
||||
},
|
||||
@ -65,7 +65,7 @@ describe('Example Tests', function() {
|
||||
`,
|
||||
scriptURLs: [
|
||||
// docsifyInit() route
|
||||
'/data-test-scripturls.js',
|
||||
'data-test-scripturls.js',
|
||||
// Server route
|
||||
'/lib/plugins/search.min.js',
|
||||
],
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
const { removeAtag } = require(`${SRC_PATH}/core/render/utils`);
|
||||
const { tree } = require(`${SRC_PATH}/core/render/tpl`);
|
||||
const { removeAtag } = require('../../src/core/render/utils');
|
||||
|
||||
const { tree } = require(`../../src/core/render/tpl`);
|
||||
|
||||
// Suite
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
const { History } = require(`${SRC_PATH}/core/router/history/base`);
|
||||
const { History } = require('../../src/core/router/history/base');
|
||||
|
||||
class MockHistory extends History {
|
||||
parse(path) {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
const { resolvePath } = require(`${SRC_PATH}/core/util`);
|
||||
const { resolvePath } = require('../../src/core/util');
|
||||
|
||||
// Suite
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user