parser-mantester
Version:
ManTester.com Parser from ManTester-enhanced Markdown to JSON data structure.
168 lines (151 loc) • 5.49 kB
JavaScript
import { unified } from 'unified'
import remarkParse from 'remark-parse'
import remarkHtml from 'remark-html'
const htmlHolder = {}
remarkHtml.call(htmlHolder, {})
const toHtml = ast => htmlHolder.Compiler(ast, {})
const debug = false
const processor = unified()
.use(remarkParse)
const isTestCaseStart = node => node.type === 'heading' && node.depth === 2
const isTestCaseEnd = node => !node || (node.type === 'heading' && node.depth <= 2)
const isTestStepsStart = node => node.type === 'heading' && node.depth === 3
const isTestStepsEnd = node => !node || node.type === 'heading'
const isList = node => node.type === 'list'
const isInTestCase = status => status === 'testCase'
const isInTestSteps = status => status === 'testSteps'
const extractTextDescendants = node => {
if (node.type === 'text') {
return [node]
}
if (node.children) {
const arrays = node.children
.map(extractTextDescendants)
return [].concat(...arrays)
}
return []
}
const extractTextFromDescendants = textDescendants =>
squashWhitespaces(
textDescendants
.map(({ value }) => value)
.join(' ')
.trim()
)
const squashWhitespaces = string => string.replace(/[ \t]+/g, ' ')
const groupRegex = /@(\w+)/g
const getGroups = text =>
[...text.matchAll(groupRegex)]
.map(([, group]) => group)
const categoryRegex = /!(\w+)/g
const getCategory = text =>
[...text.matchAll(categoryRegex)]
.map(([, group]) => group)[0]
const removeEmptyParagraphs = string =>
string.replace(/<p>\s*<\/p>/g, '')
const resultRegex = /\s\|\s([^|]+)$/g
const getResult = text => {
const matches = [...text.matchAll(resultRegex)]
.map(([, result]) => result)
if (matches.length) {
return matches[matches.length - 1]
}
}
export const parse = markdown => {
const ast = processor().parse(markdown)
// debug && console.log('index.js AST', JSON.stringify(ast, null, 2))
// debug && console.log('index.js HTML', toHtml(ast))
let status = 'outside'
const testCases = []
let testCase
const close = node => {
if (isInTestSteps(status) && isTestStepsEnd(node)) {
status = 'testCase'
}
if (isInTestCase(status) && isTestCaseEnd(node)) {
testCases.push(testCase)
debug && console.log('index.js closed test case', testCase)
}
}
for (const node of ast.children) {
// debug && console.log('index.js parse', status, JSON.stringify(node, null, 2))
close(node)
if (isTestCaseStart(node)) {
status = 'testCase'
const code = node.children
.filter(({ type }) => type === 'inlineCode')
.map(({ value }) => value)
.join('')
.trim()
const name = extractTextFromDescendants(extractTextDescendants(node))
testCase = { code, name, category: null, groups: [], description: null, steps: [] }
debug && console.log('index.js started test case', { code, name })
}
if (!isTestCaseStart(node) && isInTestCase(status)) {
const textDescendants = extractTextDescendants(node)
const text = extractTextFromDescendants(textDescendants)
const groups = getGroups(text)
// debug && console.log('index.js finding groups and category in:', text)
if (groups.length) {
if (!testCase.groups) {
testCase.groups = []
}
testCase.groups.push(...groups)
textDescendants.forEach(node => {
node.value = node.value.replace(groupRegex, '')
})
debug && console.log('index.js detected groups', groups)
}
const category = getCategory(text)
if (category) {
testCase.category = category
textDescendants.forEach(node => {
node.value = node.value.replace(categoryRegex, '')
})
debug && console.log('index.js detected category', category)
}
}
if ((!isTestCaseStart(node) && isInTestCase(status) && !isTestStepsStart(node)) ||
(isInTestSteps(status) && !isList(node))) {
if (!testCase.description) {
testCase.description = ''
}
const description = squashWhitespaces(removeEmptyParagraphs(toHtml(node)))
testCase.description += description
testCase.description = testCase.description || null
debug && console.log('index.js description', description)
}
if (isInTestCase(status) && isTestStepsStart(node)) {
status = 'testSteps'
debug && console.log('index.js started test steps')
}
if (isInTestSteps(status) && isList(node)) {
const results = []
node.children.forEach(step => {
const textDescendants = extractTextDescendants(step)
const resultDescendantIndex = textDescendants
.map(({ value }) => !!getResult(value))
.lastIndexOf(true)
if (resultDescendantIndex >= 0) {
const descendant = textDescendants[resultDescendantIndex]
results.push(getResult(descendant.value))
descendant.value = descendant.value.replace(resultRegex, '')
} else {
results.push(undefined)
}
})
const testSteps =
node.children
.map(toHtml)
.map(string => string.substring('<li>'.length, string.length - '</li>'.length))
.map((task, index) => ({ task, result: results[index] }))
testCase.steps.push(...testSteps)
debug && console.log('index.js testSteps', testSteps)
}
}
close()
return { testCases }
}
if (typeof window !== 'undefined') {
window.ManTesterParser = { parse }
}