test: set up test for core (#480)

This commit is contained in:
ULIVZ
2018-05-27 01:58:39 +08:00
committed by GitHub
parent d53807ea1e
commit f3beba674d
37 changed files with 1582 additions and 111 deletions
+9
View File
@@ -0,0 +1,9 @@
{
"env": {
"test": {
"presets": [
["@babel/preset-env", { "targets": { "node": 8 }}]
]
}
}
}
+2 -1
View File
@@ -1,7 +1,8 @@
module.exports = {
root: true,
extends: [
'plugin:vue-libs/recommended'
'plugin:vue-libs/recommended',
'plugin:jest/recommended'
],
rules: {
indent: ['error', 2, { MemberExpression: 'off' }]
+1 -1
View File
@@ -26,7 +26,7 @@ if (module.hot) {
Vue.config.productionTip = false
Vue.use(Router)
// mixin for exposing $site and $page
Vue.mixin(dataMixin)
Vue.mixin(dataMixin(siteData))
// component for rendering markdown content and setting title etc.
Vue.component('Content', Content)
Vue.component('OutboundLink', OutboundLink)
+69 -68
View File
@@ -1,7 +1,75 @@
import Vue from 'vue'
import { siteData } from './.temp/siteData'
import { findPageForPath } from './util'
export default function dataMixin (siteData) {
prepare(siteData)
const store = new Vue({
data: { siteData }
})
if (module.hot) {
module.hot.accept('./.temp/siteData', () => {
prepare(siteData)
store.siteData = siteData
})
}
return {
computed: {
$site () {
return store.siteData
},
$localeConfig () {
const { locales = {}} = this.$site
let targetLang
let defaultLang
for (const path in locales) {
if (path === '/') {
defaultLang = locales[path]
} else if (this.$page.path.indexOf(path) === 0) {
targetLang = locales[path]
}
}
return targetLang || defaultLang || {}
},
$siteTitle () {
return this.$localeConfig.title || this.$site.title || ''
},
$title () {
const page = this.$page
const siteTitle = this.$siteTitle
const selfTitle = page.frontmatter.home ? null : (
page.frontmatter.title || // explicit title
page.title // inferred title
)
return siteTitle
? selfTitle
? (siteTitle + ' | ' + selfTitle)
: siteTitle
: selfTitle || 'VuePress'
},
$description () {
return this.$page.frontmatter.description || this.$localeConfig.description || this.$site.description || ''
},
$lang () {
return this.$page.frontmatter.lang || this.$localeConfig.lang || 'en-US'
},
$localePath () {
return this.$localeConfig.path || '/'
},
$themeLocaleConfig () {
return (this.$site.themeConfig.locales || {})[this.$localePath] || {}
},
$page () {
return findPageForPath(
this.$site.pages,
this.$route.path
)
}
}
}
}
function prepare (siteData) {
siteData.pages.forEach(page => {
if (!page.frontmatter) {
@@ -15,70 +83,3 @@ function prepare (siteData) {
}
Object.freeze(siteData)
}
prepare(siteData)
const store = new Vue({
data: { siteData }
})
if (module.hot) {
module.hot.accept('./.temp/siteData', () => {
prepare(siteData)
store.siteData = siteData
})
}
export default {
computed: {
$site () {
return store.siteData
},
$localeConfig () {
const { locales = {}} = this.$site
let targetLang
let defaultLang
for (const path in locales) {
if (path === '/') {
defaultLang = locales[path]
} else if (this.$page.path.indexOf(path) === 0) {
targetLang = locales[path]
}
}
return targetLang || defaultLang || {}
},
$siteTitle () {
return this.$localeConfig.title || this.$site.title || ''
},
$title () {
const page = this.$page
const siteTitle = this.$siteTitle
const selfTitle = page.frontmatter.home ? null : (
page.frontmatter.title || // explicit title
page.title // inferred title
)
return siteTitle
? selfTitle
? (siteTitle + ' | ' + selfTitle)
: siteTitle
: selfTitle || 'VuePress'
},
$description () {
return this.$page.frontmatter.description || this.$localeConfig.description || this.$site.description || ''
},
$lang () {
return this.$page.frontmatter.lang || this.$localeConfig.lang || 'en-US'
},
$localePath () {
return this.$localeConfig.path || '/'
},
$themeLocaleConfig () {
return (this.$site.themeConfig.locales || {})[this.$localePath] || {}
},
$page () {
return findPageForPath(
this.$site.pages,
this.$route.path
)
}
}
}
+10 -6
View File
@@ -10,7 +10,7 @@ const emoji = require('markdown-it-emoji')
const anchor = require('markdown-it-anchor')
const toc = require('markdown-it-table-of-contents')
const _slugify = require('./slugify')
const { parseHeaders } = require('../util')
const parseHeaders = require('../util/parseHeaders')
module.exports = ({ markdown = {}} = {}) => {
// allow user config slugify
@@ -54,6 +54,15 @@ module.exports = ({ markdown = {}} = {}) => {
md.use(lineNumbers)
}
module.exports.dataReturnable(md)
// expose slugify
md.slugify = slugify
return md
}
module.exports.dataReturnable = function dataReturnable (md) {
// override render to allow custom plugins return data
const render = md.render
md.render = (...args) => {
@@ -64,9 +73,4 @@ module.exports = ({ markdown = {}} = {}) => {
data: md.__data
}
}
// expose slugify
md.slugify = slugify
return md
}
-2
View File
@@ -5,8 +5,6 @@ exports.encodePath = path => {
return path.split('/').map(item => encodeURIComponent(item)).join('/')
}
exports.parseHeaders = parseHeaders
exports.normalizeHeadTag = tag => {
if (typeof tag === 'string') {
tag = [tag]
+11 -3
View File
@@ -10,7 +10,8 @@
"dev": "node bin/vuepress dev docs",
"build": "node bin/vuepress build docs",
"lint": "eslint --fix --ext .js,.vue bin/ lib/ test/",
"prepublishOnly": "conventional-changelog -p angular -r 2 -i CHANGELOG.md -s"
"prepublishOnly": "conventional-changelog -p angular -r 2 -i CHANGELOG.md -s",
"test": "node test/prepare.js && jest --config test/jest.config.js"
},
"repository": {
"type": "git",
@@ -37,10 +38,10 @@
]
},
"dependencies": {
"autoprefixer": "^8.2.0",
"@babel/core": "7.0.0-beta.47",
"babel-loader": "8.0.0-beta.3",
"@vue/babel-preset-app": "3.0.0-beta.11",
"autoprefixer": "^8.2.0",
"babel-loader": "8.0.0-beta.3",
"cache-loader": "^1.2.2",
"chalk": "^2.3.2",
"chokidar": "^2.0.3",
@@ -94,10 +95,17 @@
"workbox-build": "^3.1.0"
},
"devDependencies": {
"@vue/test-utils": "^1.0.0-beta.16",
"babel-core": "^7.0.0-0",
"babel-jest": "^23.0.0",
"conventional-changelog-cli": "^1.3.22",
"eslint": "^4.19.1",
"eslint-plugin-jest": "^21.15.1",
"eslint-plugin-vue-libs": "^3.0.0",
"jest": "^23.0.0",
"jest-serializer-vue": "^1.0.0",
"lint-staged": "^7.0.4",
"vue-jest": "^2.6.0",
"vuepress-theme-vue": "^1.0.2",
"yorkie": "^1.0.3"
},
+30
View File
@@ -0,0 +1,30 @@
import Content from '@/app/Content.js'
import { mount } from '@vue/test-utils'
import { getRouter, modeTestRunner } from '../util'
function test (mode, localVue) {
it(`${mode} - add custom class by default.`, () => {
const wrapper = mount(Content, {
localVue,
router: getRouter()
})
expect(wrapper.contains('.custom')).toBe(true)
})
it(`${mode} - remove custom when custom set to false.`, () => {
const wrapper = mount(Content, {
// https://vue-test-utils.vuejs.org/api/options.html#context
context: {
props: {
custom: false
}
},
localVue,
router: getRouter()
})
expect(wrapper.contains('.custom')).toBe(false)
})
}
modeTestRunner('Content', test)
+31
View File
@@ -0,0 +1,31 @@
import { mount, RouterLinkStub } from '@vue/test-utils'
import DropdownLink from '@/default-theme/DropdownLink.vue'
import { modeTestRunner } from '../util'
function test (mode, localVue) {
it(`$${mode} - renders dropdown link.`, () => {
const item = {
text: 'VuePress',
items: [
{
text: 'Guide',
link: '/guide/'
},
{
text: 'Config Reference',
link: '/config/'
}
]
}
const wrapper = mount(DropdownLink, {
localVue,
stubs: {
'router-link': RouterLinkStub
},
propsData: { item }
})
expect(wrapper.html()).toMatchSnapshot()
})
}
modeTestRunner('DropdownLink', test)
+34
View File
@@ -0,0 +1,34 @@
import { mount, RouterLinkStub } from '@vue/test-utils'
import { modeTestRunner } from '../util'
import NavLink from '@/default-theme/NavLink.vue'
function test (mode, localVue) {
it(`$${mode} - renders nav link with internal link`, () => {
const item = {
link: '/',
text: 'VuePress'
}
const wrapper = mount(NavLink, {
localVue,
stubs: {
'router-link': RouterLinkStub
},
propsData: { item }
})
expect(wrapper.html()).toMatchSnapshot()
})
it(`$${mode} - renders nav link with external link`, () => {
const item = {
link: 'http://vuejs.org/',
text: 'Vue'
}
const wrapper = mount(NavLink, {
localVue,
propsData: { item }
})
expect(wrapper.html()).toMatchSnapshot()
})
}
modeTestRunner('NavLink', test)
@@ -0,0 +1,33 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DropdownLink $i18n - renders dropdown link. 1`] = `
<div class="dropdown-wrapper">
<a class="dropdown-title"><span class="title">VuePress</span> <span class="arrow right"></span></a>
<ul class="nav-dropdown" style="display: none;" name="dropdown">
<li class="dropdown-item">
<!---->
<a class="nav-link">Guide</a>
</li>
<li class="dropdown-item">
<!---->
<a class="nav-link">Config Reference</a>
</li>
</ul>
</div>
`;
exports[`DropdownLink $simple - renders dropdown link. 1`] = `
<div class="dropdown-wrapper">
<a class="dropdown-title"><span class="title">VuePress</span> <span class="arrow right"></span></a>
<ul class="nav-dropdown" style="display: none;" name="dropdown">
<li class="dropdown-item">
<!---->
<a class="nav-link">Guide</a>
</li>
<li class="dropdown-item">
<!---->
<a class="nav-link">Config Reference</a>
</li>
</ul>
</div>
`;
@@ -0,0 +1,19 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`NavLink $i18n - renders nav link with external link 1`] = `
<a href="http://vuejs.org/" target="_blank" rel="noopener noreferrer" class="nav-link external">
Vue
<p class="component outbound-link">outbound-link</p>
</a>
`;
exports[`NavLink $i18n - renders nav link with internal link 1`] = `<a class="nav-link">VuePress</a>`;
exports[`NavLink $simple - renders nav link with external link 1`] = `
<a href="http://vuejs.org/" target="_blank" rel="noopener noreferrer" class="nav-link external">
Vue
<p class="component outbound-link">outbound-link</p>
</a>
`;
exports[`NavLink $simple - renders nav link with internal link 1`] = `<a class="nav-link">VuePress</a>`;
+19
View File
@@ -0,0 +1,19 @@
const Component = {
props: ['name'],
render (h) {
return h('p', {
class: ['component', this.name]
}, this.name)
}
}
// When the child component is a pure presentation component,
// we want to be able to display a sub-component with minimal info,
// rather than stubbing it directly.
export function mockComponent (name) {
return {
render (h) {
return h(Component, { props: { name }})
}
}
}
+20
View File
@@ -0,0 +1,20 @@
// https://github.com/facebook/jest/tree/master/packages/babel-jest
// TODO remove 'babel-core@^7.0.0-0' when babel-jest can work with '@babel/core'
module.exports = {
verbose: true,
moduleFileExtensions: [
'js',
'vue'
],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/../lib/$1'
},
transform: {
'^.+\\.js$': '<rootDir>/../node_modules/babel-jest',
'.*\\.(vue)$': '<rootDir>/../node_modules/vue-jest'
},
snapshotSerializers: [
'<rootDir>/../node_modules/jest-serializer-vue'
]
}
@@ -0,0 +1,28 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`containers danger 1`] = `
<div class="danger custom-block">
<p class="custom-block-title">WARNING</p>
<p>I am a danger</p>
</div>
`;
exports[`containers tip 1`] = `
<div class="tip custom-block">
<p class="custom-block-title">TIP</p>
<p>I am a tip</p>
</div>
`;
exports[`containers v-pre 1`] = `
<div v-pre>
<p>I am a v-pre</p>
</div>
`;
exports[`containers warning 1`] = `
<div class="warning custom-block">
<p class="custom-block-title">WARNING</p>
<p>I am a warning</p>
</div>
`;
@@ -0,0 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`highlight should highlight code 1`] = `
<pre v-pre class="language-js"><code><span class="token keyword">new</span> <span class="token class-name">Vue</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
</code></pre>
`;
@@ -0,0 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`highlightLines highlight multiple lines 1`] = `
<div class="highlight-lines">
<div class="highlighted">&nbsp;</div>
<div class="highlighted">&nbsp;</div>
<br>
<div class="highlighted">&nbsp;</div>
<div class="highlighted">&nbsp;</div>
<br>
<br>
</div>const app = new Vue({ render, router }) app.$mount('#app')
`;
exports[`highlightLines highlight single line 1`] = `
<div class="highlight-lines">
<div class="highlighted">&nbsp;</div>
<br>
</div>new Vue()
`;
@@ -0,0 +1,30 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`hoist Should keep script and style when not using hoist 1`] = `
<h1>H1</h1>
<script src="vue.js"></script>
<style>
.vue {
font-size: 16px;
}
</style>
<h2>H2</h2>
`;
exports[`hoist Should miss script and style when using hoist 1`] = `
<h1>H1</h1>
<h2>H2</h2>
`;
exports[`hoist Should miss script and style when using hoist 2`] = `
Object {
"hoistedTags": Array [
<script src="vue.js"></script>,
<style>
.vue {
font-size: 16px;
}
</style>,
],
}
`;
@@ -0,0 +1,26 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`lineNumbers should lineNumbers work with highlightLines 1`] = `
<!--beforebegin-->
<div class="language-js line-numbers-mode">
<!--afterbegin-->
<div class="highlight-lines">
<div class="highlighted">&nbsp;</div>
<br>
</div>new Vue()
<div class="line-numbers-wrapper"></div>
<!--beforeend-->
</div>
<!--afterend-->
`;
exports[`lineNumbers should render lineNumbers 1`] = `
<!--beforebegin-->
<div class="language-js line-numbers-mode">
<!--afterbegin--><pre><code class="language-js">new Vue()
</code></pre>
<div class="line-numbers-wrapper"></div>
<!--beforeend-->
</div>
<!--afterend-->
`;
+15
View File
@@ -0,0 +1,15 @@
import { Md, getFragment } from './util'
import containers from '@/markdown/containers.js'
const mdC = Md().use(containers)
describe('containers', () => {
const containerLabels = ['tip', 'warning', 'danger', 'v-pre']
containerLabels.forEach(label => {
test(label, async () => {
const input = await getFragment(`container-${label}`)
const output = mdC.render(input)
expect(output).toMatchSnapshot()
})
})
})
@@ -0,0 +1,8 @@
``` js {1-2,4-5}
const app = new Vue({
render,
router
})
app.$mount('#app')
```
@@ -0,0 +1,3 @@
``` js {1}
new Vue()
```
+3
View File
@@ -0,0 +1,3 @@
``` js
new Vue()
```
@@ -0,0 +1,3 @@
::: danger
I am a danger
:::
+3
View File
@@ -0,0 +1,3 @@
::: tip
I am a tip
:::
@@ -0,0 +1,3 @@
::: v-pre
I am a v-pre
:::
@@ -0,0 +1,3 @@
::: warning
I am a warning
:::
+10
View File
@@ -0,0 +1,10 @@
# H1
<script src="vue.js"></script>
<style>
.vue {
font-size: 16px;
}
</style>
## H2
+15
View File
@@ -0,0 +1,15 @@
import { Md, getFragment } from './util'
import highlight from '@/markdown/highlight.js'
const md = Md()
const mdH = Md().set({ highlight })
describe('highlight', () => {
test('should highlight code', async () => {
const input = await getFragment('code')
const output1 = md.render(input)
const output2 = mdH.render(input)
expect(output1 === output2).toBe(false)
expect(output2).toMatchSnapshot()
})
})
+26
View File
@@ -0,0 +1,26 @@
import { Md, getFragment } from './util'
import highlightLines from '@/markdown/highlightLines.js'
const md = Md()
const mdH = Md().use(highlightLines)
describe('highlightLines', () => {
test('should the output not change when highlightLines is not detected', async () => {
const input = await getFragment('code')
const output1 = md.render(input)
const output2 = mdH.render(input)
expect(output1).toBe(output2)
})
test('highlight single line', async () => {
const input = await getFragment('code-highlightLines-single')
const output = mdH.render(input)
expect(output).toMatchSnapshot()
})
test('highlight multiple lines', async () => {
const input = await getFragment('code-highlightLines-multiple')
const output = mdH.render(input)
expect(output).toMatchSnapshot()
})
})
+23
View File
@@ -0,0 +1,23 @@
import { Md, getFragment } from './util'
import hoist from '@/markdown/hoist.js'
import { dataReturnable } from '@/markdown/index.js'
const md = Md().set({ html: true })
const mdH = Md().set({ html: true }).use(hoist)
dataReturnable(mdH)
describe('hoist', () => {
test('Should keep script and style when not using hoist', async () => {
const input = await getFragment('hoist')
const output = md.render(input)
expect(output).toMatchSnapshot()
})
test('Should miss script and style when using hoist', async () => {
const input = await getFragment('hoist')
const { html, data } = mdH.render(input)
expect(html).toMatchSnapshot()
expect(data).toMatchSnapshot()
})
})
+23
View File
@@ -0,0 +1,23 @@
import { Md, getFragment } from './util'
import preWrapper from '@/markdown/preWrapper.js'
import lineNumbers from '@/markdown/lineNumbers.js'
import highlightLines from '@/markdown/highlightLines.js'
// lineNumbers must be chained after preWrapper.
// since lineNumbers needs to add extra stateful class to its block wrapper.
const mdL = Md().use(preWrapper).use(lineNumbers)
const mdLH = Md().use(highlightLines).use(preWrapper).use(lineNumbers)
describe('lineNumbers', () => {
test('should render lineNumbers', async () => {
const input = await getFragment('code')
const output = mdL.render(input)
expect(output).toMatchSnapshot()
})
test('should lineNumbers work with highlightLines', async () => {
const input = await getFragment('code-highlightLines-single')
const output = mdLH.render(input)
expect(output).toMatchSnapshot()
})
})
+20
View File
@@ -0,0 +1,20 @@
import fs from 'fs-extra'
import LRU from 'lru-cache'
import path from 'path'
const cache = LRU({ max: 1000 })
export function Md () {
return require('markdown-it')()
}
export async function getFragment (name) {
let content = cache.get(name)
if (content) {
return content
}
const target = path.resolve(__dirname, `fragments/${name}.md`)
content = await fs.readFile(target, 'utf-8')
cache.set(name, content)
return content
}
+21
View File
@@ -0,0 +1,21 @@
const path = require('path')
const fs = require('fs-extra')
const prepare = require('../lib/prepare')
const logger = require('../lib/util/logger')
const tempPath = path.resolve(__dirname, '.temp')
const siteDatePath = path.resolve(__dirname, '../lib/app/.temp/siteData.js')
async function prepareForTest () {
await fs.ensureDir(tempPath)
await prepare(path.resolve(__dirname, '../docs'))
await fs.copy(siteDatePath, path.join(tempPath, 'siteData-i18n.js'))
await prepare(path.resolve(__dirname, 'docs-simple'))
await fs.copy(siteDatePath, path.join(tempPath, 'siteData-simple.js'))
}
prepareForTest().then(() => {
logger.wait('Preparing for testing ...')
})
+42
View File
@@ -0,0 +1,42 @@
import { createLocalVue } from '@vue/test-utils'
import dataMixin from '@/app/dataMixin'
import Router from 'vue-router'
import { mockComponent } from './hoc'
import { siteData as i18nSiteData } from './.temp/siteData-i18n'
import { siteData as simpleSiteData } from './.temp/siteData-simple'
export function getRouter () {
return new Router()
}
export function getLocalVueByMode (mode) {
const localVue = createLocalVue()
localVue.use(Router)
// register global component
localVue.component('OutboundLink', mockComponent('outbound-link'))
// register page component in root route.
localVue.component(i18nSiteData.pages[0].key, mockComponent('page-component'))
localVue.component(simpleSiteData.pages[0].key, mockComponent('page-component'))
// data mixin
if (mode === 'i18n') {
localVue.mixin(dataMixin(i18nSiteData))
} else {
localVue.mixin(dataMixin(simpleSiteData))
}
return localVue
}
export function modeTestRunner (description, testFn, modes = ['simple', 'i18n']) {
if (!Array.isArray(modes)) {
modes = [modes]
}
modes.forEach(mode => {
describe(description, () => {
testFn(mode, getLocalVueByMode(mode))
})
})
}
+963 -30
View File
File diff suppressed because it is too large Load Diff