Fix tests and improve CI (#590)

* Fix tests and improve CI

* Add xvfb to Linux - still problems with Mocha e2e tests

* Disable Webpack Bundle Analyzer for testing

* Use preinstalled yarn application on AppVeyor

* Fix build failure with latest vue version

* Remove "markdown-toc"

* Fix application title unit test

* Hide electron window during unit tests

* Add basic muya unit tests

* Dirty markdown-toc replacement

* Update dependencies

* Update eslint packages and configuration
This commit is contained in:
Felix Häusler 2018-12-17 03:29:37 +01:00 committed by Ran Luo
parent fa394be978
commit 2ce05829d7
35 changed files with 2176 additions and 4768 deletions

View File

@ -172,13 +172,18 @@ const rendererConfig = {
*/
if (process.env.NODE_ENV !== 'production') {
rendererConfig.plugins.push(
new BundleAnalyzerPlugin(),
new webpack.DefinePlugin({
'__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`
})
)
}
if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
rendererConfig.plugins.push(
new BundleAnalyzerPlugin()
)
}
/**
* Adjust rendererConfig for production settings
*/

View File

@ -2,24 +2,35 @@ module.exports = {
root: true,
parser: 'babel-eslint',
parserOptions: {
ecmaFeatures: {
impliedStrict: true
},
sourceType: 'module'
},
env: {
browser: true,
es6: true,
node: true
},
extends: 'standard',
extends: [
'standard',
'eslint:recommended',
'plugin:vue/base' // 'plugin:vue/essential'
],
globals: {
__static: true
},
plugins: [
'html'
'html',
'vue'
],
'rules': {
rules: {
// allow paren-less arrow functions
'arrow-parens': 0,
// allow async-await
'generator-star-spacing': 0,
// allow console
'no-console': 0,
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
}

View File

@ -1,9 +1,4 @@
# Commented sections below can be used to run tests on the CI server
# https://simulatedgreg.gitbooks.io/electron-vue/content/en/testing.html#on-the-subject-of-ci-testing
osx_image: xcode9.2
sudo: required
dist: trusty
language: node_js
node_js:
- 8
@ -11,10 +6,12 @@ node_js:
matrix:
include:
- os: osx
env: CC=clang CXX=clang++ npm_config_clang=1 MARKTEXT_IS_OFFICIAL_RELEASE=1
osx_image: xcode9.2
env: CC=clang CXX=clang++ npm_config_clang=1 MARKTEXT_IS_OFFICIAL_RELEASE=1 MARKTEXT_EXIT_ON_ERROR=1
compiler: clang
- os: linux
env: CC=clang CXX=clang++ npm_config_clang=1 MARKTEXT_IS_OFFICIAL_RELEASE=1
dist: trusty
env: CC=clang CXX=clang++ npm_config_clang=1 MARKTEXT_IS_OFFICIAL_RELEASE=1 MARKTEXT_EXIT_ON_ERROR=1 DISPLAY=:99.0
compiler: clang
cache:
@ -30,28 +27,19 @@ addons:
- icnsutils
- graphicsmagick
- xz-utils
#- xvfb
- xvfb
# atom/keyboard-layout
- libx11-dev
- libxkbfile-dev
before_install:
#- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -qq update ; fi
#- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
# wget -qO - https://dl.winehq.org/wine-builds/Release.key | sudo apt-key add -;
# sudo apt-add-repository https://dl.winehq.org/wine-builds/ubuntu/;
# fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -qq update ; fi
#- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install --install-recommends winehq-stable ; fi
#- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi
install:
#- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export DISPLAY=':99.0' ; fi
#- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & ; fi
- curl -o- -L https://yarnpkg.com/install.sh | bash
- source ~/.bashrc
- npm install -g xvfb-maybe
- $CC --version
- $CXX --version
@ -61,13 +49,17 @@ install:
- yarn
script:
#- xvfb-maybe node_modules/.bin/karma start test/unit/karma.conf.js
#- yarn run pack && xvfb-maybe node_modules/.bin/mocha test/e2e
- yarn run lint
# Unit and e2e tests
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then xvfb-run --server-args="-screen 0 1024x768x24" yarn run test ; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then yarn run test ; fi
# Build binaries
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then yarn run release:linux ; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then yarn run release:mac ; fi
# calculate checksums
# Calculate checksums
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sha256sum build/marktext-*-x64.tar.gz ; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sha256sum build/marktext-*-x86_64.AppImage ; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then shasum -a 256 build/Mark\ Text-*-mac.zip ; fi

View File

@ -14,6 +14,7 @@ skip_tags: true
environment:
MARKTEXT_IS_OFFICIAL_RELEASE: 1
MARKTEXT_EXIT_ON_ERROR: 1
GH_TOKEN:
secure: Ki5AJWygDYhzMJxl0b0rDx3bhAYmar2aPdwVHiai9IigqsvZpWHLeI3qpTiiaOWL
@ -22,7 +23,6 @@ init:
install:
- ps: Install-Product node 8 $env:PLATFORM
- choco install yarn --ignore-dependencies
- node --version
- npm --version
@ -38,6 +38,8 @@ cache:
build_script:
- yarn run lint
- yarn run test
- yarn run release:win
# calculate checksums
@ -46,4 +48,4 @@ build_script:
test: off
# test_script:
# - yarn test
# - yarn run test

6132
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -23,7 +23,7 @@
"pack:main": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.main.config.js",
"pack:renderer": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.renderer.config.js",
"test": "npm run unit && npm run e2e",
"unit": "karma start test/unit/karma.conf.js",
"unit": "cross-env NODE_ENV=test karma start test/unit/karma.conf.js",
"postinstall": "npm run rebuild && npm run lint:fix",
"build:muya": "cd src/muya && webpack --progress --colors --config webpack.config.js",
"release:muya": "npm run build:muya && cd src/muya && npm publish",
@ -135,9 +135,10 @@
"chokidar": "^2.0.4",
"codemirror": "^5.42.0",
"command-exists": "^1.2.8",
"diacritics-map": "^0.1.0",
"dompurify": "^1.0.8",
"electron-is-accelerator": "^0.1.2",
"element-resize-detector": "^1.1.14",
"element-resize-detector": "^1.2.0",
"element-ui": "^2.4.11",
"file-icons-js": "^1.0.3",
"flowchart.js": "^1.11.3",
@ -147,9 +148,8 @@
"html-tags": "^2.0.0",
"katex": "^0.10.0",
"keyboard-layout": "^2.0.14",
"markdown-toc": "^1.2.0",
"mermaid": "^8.0.0-rc.8",
"popper.js": "^1.14.5",
"popper.js": "^1.14.6",
"prismjs2": "^1.15.0",
"snabbdom": "^0.7.2",
"snabbdom-to-html": "^5.1.1",
@ -157,16 +157,16 @@
"turndown": "^5.0.1",
"turndown-plugin-gfm": "^1.0.2",
"underscore": "^1.9.1",
"vega": "^4.3.0",
"vega-embed": "^3.24.2",
"vega-lite": "^3.0.0-rc8",
"vega": "^4.4.0",
"vega-embed": "^3.26.1",
"vega-lite": "^3.0.0-rc10",
"vue": "^2.5.21",
"vue-electron": "^1.0.6",
"vuex": "^3.0.1"
},
"devDependencies": {
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.6",
"babel-eslint": "^10.0.1",
"babel-loader": "^7.1.5",
"babel-plugin-component": "^1.1.1",
"babel-plugin-istanbul": "^5.1.0",
@ -182,42 +182,43 @@
"css-loader": "^1.0.1",
"del": "^3.0.0",
"devtron": "^1.4.0",
"electron": "^3.0.10",
"electron-builder": "^20.38.1",
"electron": "^3.0.12",
"electron-builder": "^20.38.3",
"electron-debug": "^2.0.0",
"electron-devtools-installer": "^2.2.4",
"electron-rebuild": "^1.8.2",
"electron-updater": "^4.0.5",
"electron-window-state": "^5.0.2",
"eslint": "^4.19.1",
"eslint-config-standard": "^11.0.0",
"electron-updater": "^4.0.6",
"electron-window-state": "^5.0.3",
"eslint": "^5.10.0",
"eslint-config-standard": "^12.0.0",
"eslint-friendly-formatter": "^4.0.1",
"eslint-loader": "^2.1.1",
"eslint-plugin-html": "^4.0.6",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-node": "^6.0.1",
"eslint-plugin-promise": "^3.8.0",
"eslint-plugin-standard": "^3.1.0",
"eslint-plugin-node": "^8.0.0",
"eslint-plugin-promise": "^4.0.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^5.0.0",
"file-loader": "^2.0.0",
"git-revision-webpack-plugin": "^3.0.3",
"html-webpack-plugin": "^3.2.0",
"inject-loader": "^4.0.1",
"karma": "^1.3.0",
"karma": "^3.1.3",
"karma-chai": "^0.1.0",
"karma-coverage": "^1.1.1",
"karma-electron": "^5.3.0",
"karma-electron": "^6.0.0",
"karma-mocha": "^1.2.0",
"karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "^0.0.31",
"karma-webpack": "^2.0.1",
"karma-spec-reporter": "0.0.32",
"karma-webpack": "^3.0.5",
"mini-css-extract-plugin": "^0.4.5",
"mocha": "^3.0.2",
"mocha": "^5.2.0",
"multispinner": "^0.2.1",
"node-loader": "^0.6.0",
"postcss-loader": "^3.0.0",
"postcss-preset-env": "^6.4.0",
"require-dir": "^1.1.0",
"spectron": "^4.0.0",
"postcss-preset-env": "^6.5.0",
"require-dir": "^1.2.0",
"spectron": "^5.0.0",
"style-loader": "^0.23.1",
"svg-sprite-loader": "^4.1.3",
"svgo": "^1.1.1",
@ -228,11 +229,11 @@
"vue-loader": "^15.4.2",
"vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.5.21",
"webpack": "^4.26.0",
"webpack": "^4.27.1",
"webpack-bundle-analyzer": "^3.0.3",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.10",
"webpack-hot-middleware": "^2.24.3",
"webpack-merge": "^4.1.4"
"webpack-merge": "^4.1.5"
}
}

View File

@ -66,10 +66,11 @@ const handleError = (title, error) => {
})
switch (result) {
case 1:
case 1: {
clipboard.writeText(`${title}\n${stack}`)
break
case 2:
}
case 2: {
const issueTitle = message ? `Unexpected error: ${message}` : title
const gitInfo = global.MARKTEXT_IS_OFFICIAL_RELEASE ? `(${global.MARKTEXT_GIT_INFO} - git)` : global.MARKTEXT_GIT_INFO
createAndOpenGitHubIssueUrl(
@ -90,6 +91,7 @@ Mark Text: ${app.getVersion()} (${gitInfo})
Operating system: ${process.platform}`)
break
}
}
} else {
// error during Electron initialization
dialog.showErrorBox(title, stack)

View File

@ -307,7 +307,8 @@ const enterCtrl = ContentState => {
let newBlock
switch (true) {
case left !== 0 && right !== 0: // cursor in the middle
case left !== 0 && right !== 0: {
// cursor in the middle
let { pre, post } = selection.chopHtmlByCursor(paragraph)
if (/^h\d$/.test(block.type)) {
@ -335,10 +336,14 @@ const enterCtrl = ContentState => {
}
this.insertAfter(newBlock, block)
break
case left === 0 && right === 0: // paragraph is empty
}
case left === 0 && right === 0: {
// paragraph is empty
return this.enterInEmptyParagraph(block)
case left !== 0 && right === 0: // cursor at end of paragraph
case left === 0 && right !== 0: // cursor at begin of paragraph
}
case left !== 0 && right === 0:
case left === 0 && right !== 0: {
// cursor at end of paragraph or at begin of paragraph
if (type === 'li') {
if (block.listItemType === 'task') {
const { checked } = block.children[0]
@ -368,11 +373,13 @@ const enterCtrl = ContentState => {
this.insertAfter(newBlock, block)
}
break
default:
}
default: {
newBlock = this.createBlockP()
this.insertAfter(newBlock, block)
break
}
}
const getParagraphBlock = block => {
if (block.type === 'li') {

View File

@ -49,23 +49,26 @@ const clearFormat = (token, { start, end }) => {
case 'strong':
case 'del':
case 'em':
case 'link':
case 'link': {
const { parent } = token
const index = parent.indexOf(token)
parent.splice(index, 1, ...token.children)
break
case 'image':
}
case 'image': {
token.type = 'text'
token.raw = token.alt
delete token.marker
delete token.src
break
case 'inline_code':
}
case 'inline_code': {
token.type = 'text'
token.raw = token.content
delete token.marker
break
}
}
}
const addFormat = (type, block, { start, end }) => {

View File

@ -55,7 +55,7 @@ const htmlBlock = ContentState => {
const htmlBlock = this.getParent(codeBlockContainer)
switch (type) {
case 'delete':
case 'delete': {
htmlBlock.type = 'p'
htmlBlock.text = ''
htmlBlock.children = []
@ -71,6 +71,7 @@ const htmlBlock = ContentState => {
break
}
}
}
ContentState.prototype.handleHtmlBlockClick = function (codeWrapper) {
const id = codeWrapper.id

View File

@ -241,7 +241,7 @@ const pasteCtrl = ContentState => {
lastBlock.text += cacheText
switch (pasteType) {
case 'MERGE':
case 'MERGE': {
if (LIST_REG.test(firstFragment.type)) {
const listItems = firstFragment.children
const firstListItem = listItems[0]
@ -295,8 +295,8 @@ const pasteCtrl = ContentState => {
})
}
break
case 'NEWLINE':
}
case 'NEWLINE': {
let target = startBlock.type === 'span' ? parent : startBlock
stateFragments.forEach(block => {
this.insertAfter(block, target)
@ -307,9 +307,11 @@ const pasteCtrl = ContentState => {
if (this.isOnlyChild(startBlock) && startBlock.type === 'span') this.removeBlock(parent)
}
break
default:
}
default: {
throw new Error('unknown paste type')
}
}
// step 3: set cursor and render
let cursorBlock = this.getBlock(key)
if (!cursorBlock) {

View File

@ -1,6 +1,7 @@
import katex from 'katex'
import mermaid from 'mermaid'
import prism, { loadedCache } from '../../../prism/'
import { slugify } from '../../../utils/dirtyToc'
import { CLASS_OR_ID, DEVICE_MEMORY, isInElectron, PREVIEW_DOMPURIFY_CONFIG, HAS_TEXT_BLOCK_REG } from '../../../config'
import { tokenizer } from '../../parse'
import { snakeToCamel, sanitize, escapeHtml, getLongUniqueId } from '../../../utils'
@ -186,9 +187,11 @@ export default function renderLeafBlock (block, cursor, activeBlocks, matches, u
]
} else if (/^h/.test(type)) {
if (/^h\d$/.test(type)) {
// TODO: This should be the best place to create and update the TOC.
// Cache `block.key` and title and update only if necessary.
Object.assign(data.dataset, {
head: type,
id: isInElectron ? require('markdown-toc').slugify(text.replace(/^#+\s(.*)/, (_, p1) => p1)) : ''
id: isInElectron ? slugify(text.replace(/^#+\s(.*)/, (_, p1) => p1)) : ''
})
selector += `.${headingStyle}`
}

View File

@ -0,0 +1,39 @@
import { Lexer } from '../parser/marked'
import diacritics from 'diacritics-map'
export const getTocFromMarkdown = markdown => {
const tokens = new Lexer({ disableInline: true }).lex(markdown)
const toc = []
let token = null
while ((token = tokens.shift())) {
switch (token.type) {
case 'heading': {
const { depth, text } = token
toc.push({
content: text,
lvl: depth,
slug: slugify(text)
})
break
}
}
}
return toc
}
export const slugify = str => {
str = str.replace(/^\s+|\s+$/g, '').toLowerCase()
// replace accents
str = str.replace(/[À-ž]/g, c => {
return diacritics[c] || c
})
return str
.replace(/[^a-z0-9 -]/g, '') // remove invalid chars
.replace(/\t/g, '--') // collapse tabs and replace by --
.replace(/\s+/g, '-') // collapse whitespace and replace by -
.replace(/^-+/, '') // trim - from start of text
.replace(/-+$/, '') // trim - from end of text
.replace(/-+/g, '-') // collapse dashes
}

View File

@ -26,52 +26,56 @@ class ExportMarkdown {
for (const block of blocks) {
switch (block.type) {
case 'p':
case 'p': {
this.insertLineBreak(result, indent, true)
result.push(this.translateBlocks2Markdown(block.children, indent))
break
case 'span':
}
case 'span': {
result.push(this.normalizeParagraphText(block, indent))
break
case 'hr':
}
case 'hr': {
this.insertLineBreak(result, indent, true)
result.push(this.normalizeParagraphText(block, indent))
break
}
case 'h1':
case 'h2':
case 'h3':
case 'h4':
case 'h5':
case 'h6':
case 'h6': {
this.insertLineBreak(result, indent, true)
result.push(this.normalizeHeaderText(block, indent))
break
case 'figure':
}
case 'figure': {
this.insertLineBreak(result, indent, true)
switch (block.functionType) {
case 'table':
case 'table': {
const table = block.children[1]
result.push(this.normalizeTable(table, indent))
break
case 'html':
}
case 'html': {
result.push(this.normalizeHTML(block, indent))
break
case 'multiplemath':
}
case 'multiplemath': {
result.push(this.normalizeMultipleMath(block, indent))
break
}
case 'mermaid':
case 'flowchart':
case 'sequence':
case 'vega-lite':
case 'vega-lite': {
result.push(this.normalizeContainer(block, indent))
break
}
}
break
}
case 'li': {
const insertNewLine = block.isLooseListItem
@ -83,7 +87,6 @@ class ExportMarkdown {
this.isLooseParentList = true
break
}
case 'ul': {
const insertNewLine = this.isLooseParentList
this.isLooseParentList = true
@ -94,7 +97,6 @@ class ExportMarkdown {
this.listType.pop()
break
}
case 'ol': {
const insertNewLine = this.isLooseParentList
this.isLooseParentList = true
@ -106,8 +108,7 @@ class ExportMarkdown {
this.listType.pop()
break
}
case 'pre':
case 'pre': {
this.insertLineBreak(result, indent, true)
if (block.functionType === 'frontmatter') {
result.push(this.normalizeFrontMatter(block, indent))
@ -115,16 +116,18 @@ class ExportMarkdown {
result.push(this.normalizeCodeBlock(block, indent))
}
break
case 'blockquote':
}
case 'blockquote': {
this.insertLineBreak(result, indent, true)
result.push(this.normalizeBlockquote(block, indent))
break
default:
}
default: {
console.log(block.type)
break
}
}
}
return result.join('')
}

View File

@ -157,13 +157,14 @@
handleInput (event) {
let historyIndex = this.historyIndex
switch (event.key) {
case 'Enter':
case 'Enter': {
const query = this.historyIndex !== -1 ? this.history[this.historyIndex] : this.query
if (!this.aiLoading) {
this.search(query)
}
break
case 'ArrowUp':
}
case 'ArrowUp': {
historyIndex = historyIndex - 1
if (historyIndex === -1 || historyIndex === -2) {
this.historyIndex = this.history.length - 1
@ -171,7 +172,8 @@
this.historyIndex = historyIndex
}
break
case 'ArrowDown':
}
case 'ArrowDown': {
historyIndex = historyIndex + 1
if (historyIndex >= this.history.length) {
this.historyIndex = 0
@ -180,6 +182,7 @@
}
break
}
}
},
handleShowAiDou () {
this.showAiDou = true

View File

@ -36,7 +36,7 @@ export const showContextMenu = (event, { start, end }) => {
)
}
;[CUT, COPY, COPY_AS_HTML, COPY_AS_MARKDOWN].forEach(item => {
[CUT, COPY, COPY_AS_HTML, COPY_AS_MARKDOWN].forEach(item => {
item.enabled = !disableCutAndCopy
})

View File

@ -5,7 +5,9 @@ import { hasKeys } from '../util'
import { getOptionsFromState, getSingleFileState, getBlankFileState } from './help'
import notice from '../services/notification'
const toc = require('markdown-toc')
// HACK: When rewriting muya, create and update muya's TOC during heading parsing and pass it to the renderer process.
import { getTocFromMarkdown } from 'muya/lib/utils/dirtyToc'
const state = {
lineEnding: 'lf',
currentFile: {},
@ -15,8 +17,8 @@ const state = {
const getters = {
toc: state => {
const { currentFile } = state
return toc(currentFile.markdown).json
const { markdown } = state.currentFile
return getTocFromMarkdown(markdown)
}
}
@ -258,15 +260,13 @@ const actions = {
LISTEN_FOR_SAVE_CLOSE ({ commit, state }) {
ipcRenderer.on('AGANI::save-all-response', (e, { err, data }) => {
if (err) {
} else if (Array.isArray(data)) {
if (!err && Array.isArray(data)) {
const toBeClosedTabs = [...state.tabs.filter(f => f.isSaved), ...data]
commit('CLOSE_TABS', toBeClosedTabs)
}
})
ipcRenderer.on('AGANI::save-single-response', (e, { err, data }) => {
if (err) {
} else if (Array.isArray(data) && data.length) {
if (!err && Array.isArray(data) && data.length) {
commit('CLOSE_TABS', data)
}
})

View File

@ -119,7 +119,7 @@ const actions = {
LISTEN_FOR_UPDATE_PROJECT ({ commit, state, dispatch }) {
ipcRenderer.on('AGANI::update-object-tree', (e, { type, change }) => {
switch (type) {
case 'add':
case 'add': {
const { pathname, data, isMarkdown } = change
commit('ADD_FILE', change)
if (isMarkdown && state.newFileNameCache && pathname === state.newFileNameCache) {
@ -128,6 +128,7 @@ const actions = {
commit('SET_NEWFILENAME', '')
}
break
}
case 'unlink':
commit('UNLINK_FILE', change)
commit('SET_SAVE_STATUS_WHEN_REMOVE', change)

View File

@ -7,7 +7,8 @@ describe('Launch', function () {
it('shows the proper application title', function () {
return this.app.client.getTitle()
.then(title => {
expect(title).to.equal('aganippe')
const expectedTitle = process.platform === 'darwin' ? 'Mark Text' : 'Untitled-1'
expect(title).to.equal(expectedTitle)
})
})
})

View File

@ -3,21 +3,20 @@ import { Application } from 'spectron'
export default {
afterEach () {
this.timeout(10000)
this.timeout(20000)
if (this.app && this.app.isRunning()) {
return this.app.stop()
}
},
beforeEach () {
this.timeout(10000)
this.timeout(20000)
this.app = new Application({
path: electron,
args: ['dist/electron/main.js'],
startTimeout: 10000,
waitTimeout: 10000
startTimeout: 20000,
waitTimeout: 20000
})
return this.app.start()
}
}

View File

@ -0,0 +1,28 @@
# Basic Text Formatting
*this is in italic* and _so is this_
**this is in bold** and __so is this__
***this is bold and italic*** and ___so is this___
<b>this will be bold</b>
<i>this will be bold</i>
<s>this is strike through text</s>
## Paragraph
A two trailing spaces and a new line
makes a line break.
Two new lines make a new paragraph.
## Failing Tests
```
So _a_ single _word_ followed _b_y _a_nother
So __a__ single __word__ followed __b__y __a__nother
```

View File

@ -0,0 +1,46 @@
# Blockquotes
> Use it if you're quoting a person, a song or whatever.
foo
> Lorem Ipsum is simply dummy text of the printing and typesetting industry.
> Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.
> It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.
- foo
- > bar
- baz
## Failing Tests
```
> You can use *italic* or lists inside them also.
And just like with other paragraphs,
all of these lines are still
part of the blockquote, even without the > character in front.
To end the blockquote, just put a blank line before the following
paragraph.
```
```
> - 1
> - 1
> - 1
```
```
* foo
> This is a blockquote
> inside a list item.
* bar`
```
```
> Use it if you're quoting a person, a song or whatever.
> You can use *italic* or lists inside them also.
```

View File

@ -0,0 +1,23 @@
# Code Blocks
This line won't *have any markdown* formatting applied.
I can even write <b>HTML</b> and it will show up as text.
This is great for showing program source code, or HTML or even
Markdown. <b>this won't show up as HTML</b> but
exactly <i>as you see it in this text file</i>.
Within a paragraph, you can use backquotes to do the same thing.
`This won't be *italic* or **bold** at all.`
```cpp
#include <iostream>
int main(int argc, const char* argv[]) {
std::cout << "C++ code block test" << std::endl;
return 0;
}
```
```
This is a code block without language identifier.
```

View File

@ -0,0 +1,11 @@
# Escapes
\# This isn't a heading
\*this isn't in italic\* and \_so is this\_
\*\*this isn't in bold\*\* and \_\_so is this\_\_
\`\`\`
This isn't a code block without language identifier.
\`\`\`

View File

@ -0,0 +1,72 @@
# Headings
This is a huge header
===
this is a smaller header
---
header
---
## ATX Headings
# foo
bar
## foo
bar
### foo
bar
#### foo
bar
##### foo
bar
###### foo
bar
####### This isn't a heading
bar
#5 This isn't a heading
#This isn't a heading
## Horizontal Rule
---
foo
---
bar
## Failing Tests
Headings and horizontal rules are shrinked to three characters because this simplify the parsing - maybe we should change this.
```
This is a huge header
==================
this is a smaller header
------------------
```
```
## Horizontal Rule
----------------
```

View File

@ -0,0 +1,3 @@
# Images
![alternate text](https://raw.githubusercontent.com/marktext/marktext/master/resources/icons/128x128/marktext.png)

View File

@ -0,0 +1,24 @@
# Links
[title](http://127.0.0.1)
[title with spaces](https://localhost)
- [title](http://127.0.0.1)
- [title with spaces](https://localhost)
- [ ] [title](http://127.0.0.1)
- [x] [title with spaces](https://localhost)
## Reference Links
You can also put the [link URL][1] below the current paragraph like [this][2].
[1]: http://url.local
[2]: http://another.url
Or you can use a [shortcut][] reference, which links the text "shortcut" to the link named "[shortcut]" on the next paragraph.
[shortcut]: http://goes/with/the/link/name/text

View File

@ -0,0 +1,105 @@
# Lists
* an asterisk starts an unordered list
* and this is another item in the list
* and this is another item in the list
To start an ordered list, write this:
<!-- Strange indentation -->
1. this starts a list *with* numbers
2. this will show as number "2"
3. this will show as number "3."
4. any number, +, -, or * will keep the list going.
* just indent by 4 spaces (or tab) to make a sub-list
1. keep indenting for more sub lists
* here i'm back to the second level
---
- foo
- bar
- baz
- boo
---
- a
- b
- c
- d
- e
- f
- g
- h
- i
---
- foo
-
- bar
---
**TODO:** Empty comments should not be displayed as HTML in preview mode because they may be used to separate consecutive lists of the same type (CM Example 270).
- foo
- bar
<!-- -->
- baz
- bim
---
-one
2.two
---
## Failing Tests
```
* an asterisk starts an unordered list
* and this is another item in the list
+ or you can also use the + character
- or the - character
```
```
1. this starts a list *with* numbers
+ this will show as number "2"
* this will show as number "3."
9. any number, +, -, or * will keep the list going.
* just indent by 4 spaces (or tab) to make a sub-list
1. keep indenting for more sub lists
* here i'm back to the second level
```
```
1. this starts a list *with* numbers
+ this will show as number "2"
* this will show as number "3."
9. any number, +, -, or * will keep the list going.
* just indent by 2 spaces to make a sub-list
1. keep indenting for more sub lists
* here i'm back to the second level
```
```
CommonMark Example 266
Foo
- bar
- baz
```
```
- foo
-
- bar
```

View File

@ -0,0 +1,3 @@
# Basic Text Formatting
~~this is strike through text~~

View File

@ -0,0 +1,7 @@
# GFM Lists
To start a check list, write this:
- [ ] this is not checked
- [ ] this is too
- [x] but this is checked

View File

@ -0,0 +1,15 @@
# Tables
| First Header | Second Header |
| -------------- | ---------------- |
| Co`nten`t Cell | Content Cell |
| Content Cell | **Content Cell** |
## Failing Tests
```
First Header | Second Header
------------ | -------------
Content Cell | Content Cell
Content Cell | Content Cell
```

View File

@ -24,13 +24,15 @@ delete webpackConfig.entry
delete webpackConfig.externals
delete webpackConfig.output.libraryTarget
// apply vue option to apply isparta-loader on js
webpackConfig.module.rules
.find(rule => rule.use.loader === 'vue-loader').use.options.loaders.js = 'babel-loader'
// BUG: TypeError: Cannot read property 'loaders' of undefined
// // apply vue option to apply isparta-loader on js
// webpackConfig.module.rules
// .find(rule => rule.use.loader === 'vue-loader').use.options.loaders.js = 'babel-loader'
module.exports = config => {
config.set({
browsers: ['visibleElectron'],
browsers: ['Electron'],
mode: 'development',
client: {
useIframe: false
},
@ -41,12 +43,6 @@ module.exports = config => {
{ type: 'text-summary' }
]
},
customLaunchers: {
'visibleElectron': {
base: 'Electron',
flags: ['--show']
}
},
frameworks: ['mocha', 'chai'],
files: ['./index.js'],
preprocessors: {

54
test/unit/markdown.js Normal file
View File

@ -0,0 +1,54 @@
import fs from 'fs'
import path from 'path'
const loadMarkdownContent = pathname => {
return fs.readFileSync(path.resolve('test/unit/data', pathname), 'utf-8')
}
export const BasicTextFormattingTemplate = () => {
return loadMarkdownContent('common/BasicTextFormatting.md')
}
export const BlockquotesTemplate= () => {
return loadMarkdownContent('common/Blockquotes.md')
}
export const CodeBlocksTemplate = () => {
return loadMarkdownContent('common/CodeBlocks.md')
}
export const EscapesTemplate = () => {
return loadMarkdownContent('common/Escapes.md')
}
export const HeadingsTemplate = () => {
return loadMarkdownContent('common/Headings.md')
}
export const ImagesTemplate = () => {
return loadMarkdownContent('common/Images.md')
}
export const LinksTemplate = () => {
return loadMarkdownContent('common/Links.md')
}
export const ListsTemplate = () => {
return loadMarkdownContent('common/Lists.md')
}
// --------------------------------------------------------
// GFM templates
//
export const GfmBasicTextFormattingTemplate = () => {
return loadMarkdownContent('gfm/BasicTextFormatting.md')
}
export const GfmListsTemplate = () => {
return loadMarkdownContent('gfm/Lists.md')
}
export const GfmTablesTemplate = () => {
return loadMarkdownContent('gfm/Tables.md')
}

View File

@ -0,0 +1,104 @@
import ContentState from '../../../src/muya/lib/contentState'
import EventCenter from '../../../src/muya/lib/eventHandler/event'
import ExportMarkdown from '../../../src/muya/lib/utils/exportMarkdown'
import { MUYA_DEFAULT_OPTION } from '../../../src/muya/lib/config'
import * as templates from '../markdown'
const defaultOptions = { 'endOfLine': 'lf' }
const defaultOptionsCrlf = Object.assign({}, defaultOptions, { 'endOfLine': 'crlf' })
const createMuyaContext = options => {
const ctx = {}
ctx.options = Object.assign({}, MUYA_DEFAULT_OPTION, options)
ctx.eventCenter = new EventCenter()
ctx.contentState = new ContentState(ctx, ctx.options)
return ctx
}
// ----------------------------------------------------------------------------
// Muya parser (Markdown to HTML to Markdown)
//
const verifyMarkdown = (markdown, options) => {
const ctx = createMuyaContext(options)
ctx.contentState.importMarkdown(markdown)
const blocks = ctx.contentState.getBlocks()
const exportedMarkdown = new ExportMarkdown(blocks).generate()
// FIXME: We always need to add a new line at the end of the document. Add a option to disable the new line.
// Muya always use LF line endings.
expect(exportedMarkdown).to.equal(markdown)
}
describe('Muya parser', () => {
it('Basic Text Formatting', () => {
verifyMarkdown(templates.BasicTextFormattingTemplate(), defaultOptions)
})
it('Blockquotes', () => {
verifyMarkdown(templates.BlockquotesTemplate(), defaultOptions)
})
it('Code Blocks', () => {
verifyMarkdown(templates.CodeBlocksTemplate(), defaultOptions)
})
it('Escapes', () => {
verifyMarkdown(templates.EscapesTemplate(), defaultOptions)
})
it('Headings', () => {
verifyMarkdown(templates.HeadingsTemplate(), defaultOptions)
})
it('Images', () => {
verifyMarkdown(templates.ImagesTemplate(), defaultOptions)
})
it('Links', () => {
verifyMarkdown(templates.LinksTemplate(), defaultOptions)
})
it('Lists', () => {
verifyMarkdown(templates.ListsTemplate(), defaultOptions)
})
it('GFM - Basic Text Formatting', () => {
verifyMarkdown(templates.GfmBasicTextFormattingTemplate(), defaultOptions)
})
it('GFM - Lists', () => {
verifyMarkdown(templates.GfmListsTemplate(), defaultOptions)
})
it('GFM - Tables', () => {
verifyMarkdown(templates.GfmTablesTemplate(), defaultOptions)
})
})
describe('Muya parser (CRLF)', () => {
it('Basic Text Formatting', () => {
verifyMarkdown(templates.BasicTextFormattingTemplate(), defaultOptionsCrlf)
})
it('Blockquotes', () => {
verifyMarkdown(templates.BlockquotesTemplate(), defaultOptionsCrlf)
})
it('Code Blocks', () => {
verifyMarkdown(templates.CodeBlocksTemplate(), defaultOptionsCrlf)
})
it('Escapes', () => {
verifyMarkdown(templates.EscapesTemplate(), defaultOptionsCrlf)
})
it('Headings', () => {
verifyMarkdown(templates.HeadingsTemplate(), defaultOptionsCrlf)
})
it('Images', () => {
verifyMarkdown(templates.ImagesTemplate(), defaultOptionsCrlf)
})
it('Links', () => {
verifyMarkdown(templates.LinksTemplate(), defaultOptionsCrlf)
})
it('Lists', () => {
verifyMarkdown(templates.ListsTemplate(), defaultOptionsCrlf)
})
it('GFM - Basic Text Formatting', () => {
verifyMarkdown(templates.GfmBasicTextFormattingTemplate(), defaultOptionsCrlf)
})
it('GFM - Lists', () => {
verifyMarkdown(templates.GfmListsTemplate(), defaultOptionsCrlf)
})
it('GFM - Tables', () => {
verifyMarkdown(templates.GfmTablesTemplate(), defaultOptionsCrlf)
})
})