├── .eslintignore ├── .gitignore ├── test ├── fixtures │ ├── empty.txt │ ├── meta.txt │ ├── multilines.txt │ └── headers.txt └── test.js ├── .npmignore ├── .travis.yml ├── package.json ├── Makefile ├── CHANGELOG.md ├── LICENSE ├── README.md ├── .eslintrc └── index.js /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | node_modules 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | *.log 4 | -------------------------------------------------------------------------------- /test/fixtures/empty.txt: -------------------------------------------------------------------------------- 1 | . 2 | a 3 | . 4 | . 5 | 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | test/ 3 | Makefile 4 | .* 5 | *.log 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '8' 4 | - node 5 | -------------------------------------------------------------------------------- /test/fixtures/meta.txt: -------------------------------------------------------------------------------- 1 | --- 2 | desc: 123 3 | skip: true 4 | --- 5 | 6 | . 7 | 123 8 | . 9 | 456 10 | . -------------------------------------------------------------------------------- /test/fixtures/multilines.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | . 6 | 123 7 | 8 | 456 9 | . 10 | 789 11 | 12 | 098 13 | . -------------------------------------------------------------------------------- /test/fixtures/headers.txt: -------------------------------------------------------------------------------- 1 | not a header 2 | 3 | 4 | . 5 | 123 6 | . 7 | 456 8 | . 9 | 10 | asdfsgawersfhdh 11 | adsffadf 12 | 13 | dfghjk 14 | header1 15 | . 16 | qwe 17 | . 18 | rty 19 | . 20 | 21 | 22 | 23452525 23 | header2 24 | 25 | . 26 | zxc 27 | . 28 | vbn 29 | . -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "markdown-it-testgen", 3 | "version": "0.1.6", 4 | "description": "Tests generator for markdown-it markdown parser", 5 | "keywords": [ 6 | "markdown-it" 7 | ], 8 | "repository": "markdown-it/markdown-it-testgen", 9 | "license": "MIT", 10 | "scripts": { 11 | "test": "make test" 12 | }, 13 | "dependencies": { 14 | "chai": "^1.10.0", 15 | "js-yaml": "^3.13.1", 16 | "object-assign": "^4.1.1" 17 | }, 18 | "devDependencies": { 19 | "eslint": "^3.5.0", 20 | "istanbul": "^0.4.5", 21 | "mocha": "^5.2.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PATH := ./node_modules/.bin:${PATH} 2 | 3 | NPM_PACKAGE := $(shell node -e 'process.stdout.write(require("./package.json").name)') 4 | NPM_VERSION := $(shell node -e 'process.stdout.write(require("./package.json").version)') 5 | 6 | TMP_PATH := /tmp/${NPM_PACKAGE}-$(shell date +%s) 7 | 8 | REMOTE_NAME ?= origin 9 | REMOTE_REPO ?= $(shell git config --get remote.${REMOTE_NAME}.url) 10 | 11 | CURR_HEAD := $(firstword $(shell git show-ref --hash HEAD | cut -b -6) master) 12 | GITHUB_PROJ := https://github.com//markdown-it/${NPM_PACKAGE} 13 | 14 | 15 | lint: 16 | eslint . 17 | 18 | test: lint 19 | mocha -R spec 20 | 21 | coverage: 22 | rm -rf coverage 23 | istanbul cover node_modules/.bin/_mocha 24 | 25 | .PHONY: lint test coverage 26 | .SILENT: lint test 27 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 0.1.6 / 2019-07-09 2 | ------------------ 3 | 4 | - Maintenance release. 5 | - Deps bump. 6 | - Remove outdated node versions from travis. 7 | 8 | 9 | 0.1.5 / 2019-03-12 10 | ------------------ 11 | 12 | - Maintenance release. 13 | - Drop lodash use. 14 | - Pin dev deps. 15 | - Add latest node to travis. 16 | - Cleanup `package.json`. 17 | 18 | 19 | 0.1.4 / 2015-01-12 20 | ------------------ 21 | 22 | - Fixed swapped params in error reports. 23 | 24 | 25 | 0.1.3 / 2015-01-12 26 | ------------------ 27 | 28 | - Added option to pass custom assertion object. 29 | - Use `chai` assertions by default. 30 | 31 | 32 | 0.1.2 / 2014-12-24 33 | ------------------ 34 | 35 | - One more maintenance release :) 36 | 37 | 38 | 0.1.1 / 2014-12-22 39 | ------------------ 40 | 41 | - Maintenance release. 42 | 43 | 44 | 0.1.0 / 2014-12-21 45 | ------------------ 46 | 47 | - First release. 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Vitaly Puzrin. 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # markdown-it-testgen 2 | 3 | [![Build Status](https://img.shields.io/travis/markdown-it/markdown-it-testgen/master.svg?style=flat)](https://travis-ci.org/markdown-it/markdown-it-testgen) 4 | [![NPM version](https://img.shields.io/npm/v/markdown-it-testgen.svg?style=flat)](https://www.npmjs.org/package/markdown-it-testgen) 5 | 6 | 7 | > This package parses fixtures in commonmark spec format and generates tests for 8 | [markdown-it](https://github.com/markdown-it/markdown-it) parser and 9 | [plugins](https://www.npmjs.org/browse/keyword/markdown-it-plugin). 10 | 11 | 12 | ```bash 13 | npm install markdown-it-testgen 14 | ``` 15 | 16 | 17 | ## Fixture format 18 | 19 | Each fixture can have optional metadata in yaml format: 20 | 21 | ```yaml 22 | --- 23 | desc: Batch description # file name used if not exists. 24 | skip: true # mark batch as pending 25 | --- 26 | ``` 27 | 28 | Then tests should follow in this format: 29 | 30 | ``` 31 | optional header 32 | . 33 | source 34 | data 35 | . 36 | parsed 37 | data 38 | . 39 | 40 | 41 | header2 42 | . 43 | src 44 | . 45 | result 46 | . 47 | ``` 48 | 49 | If header missed - line number will be used instead. 50 | 51 | 52 | ## API 53 | 54 | ### module.exports(path, options, md) 55 | 56 | - __path__ - file or directory name 57 | - __options__ (not mandatory) 58 | - __header__ - Default `false`. Set `true` to use heaters for test names 59 | - __sep__ - array of allowed separators for samples, [ '.' ] by default 60 | - __assert__ - custom assertion package, `require('chai').assert` by default. 61 | - __md__ - `markdown-it` instance to parse and compare samples 62 | 63 | ### module.exports.load(path, options, iterator) 64 | 65 | For each loaded file - parse and pass data to iterator functions. Currently used for tests only. 66 | 67 | 68 | ## License 69 | 70 | [MIT](https://github.com/markdown-it/markdown-it-testgen/blob/master/LICENSE) 71 | 72 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | /* global it, describe */ 2 | 'use strict'; 3 | 4 | 5 | var p = require('path'); 6 | var assert = require('assert'); 7 | var testgen = require('..'); 8 | 9 | 10 | describe('Generator', function () { 11 | 12 | it('should parse meta', function (done) { 13 | testgen.load(p.join(__dirname, 'fixtures/meta.txt'), function (data) { 14 | assert.deepEqual(data.meta, { desc: 123, skip: true }); 15 | 16 | assert.strictEqual(data.fixtures.length, 1); 17 | assert.strictEqual(data.fixtures[0].first.text, '123\n'); 18 | assert.strictEqual(data.fixtures[0].second.text, '456\n'); 19 | done(); 20 | }); 21 | }); 22 | 23 | it('should parse headers', function (done) { 24 | testgen.load(p.join(__dirname, 'fixtures/headers.txt'), function (data) { 25 | 26 | assert.strictEqual(data.fixtures.length, 3); 27 | 28 | assert.strictEqual(data.fixtures[0].header, ''); 29 | assert.strictEqual(data.fixtures[0].first.text, '123\n'); 30 | assert.strictEqual(data.fixtures[0].second.text, '456\n'); 31 | 32 | assert.strictEqual(data.fixtures[1].header, 'header1'); 33 | assert.strictEqual(data.fixtures[1].first.text, 'qwe\n'); 34 | assert.strictEqual(data.fixtures[1].second.text, 'rty\n'); 35 | 36 | assert.strictEqual(data.fixtures[2].header, 'header2'); 37 | assert.strictEqual(data.fixtures[2].first.text, 'zxc\n'); 38 | assert.strictEqual(data.fixtures[2].second.text, 'vbn\n'); 39 | 40 | done(); 41 | }); 42 | }); 43 | 44 | it('should parse multilines', function (done) { 45 | testgen.load(p.join(__dirname, 'fixtures/multilines.txt'), function (data) { 46 | 47 | assert.strictEqual(data.fixtures.length, 1); 48 | 49 | assert.strictEqual(data.fixtures[0].header, ''); 50 | assert.strictEqual(data.fixtures[0].first.text, '123\n \n456\n'); 51 | assert.strictEqual(data.fixtures[0].second.text, '789\n\n098\n'); 52 | 53 | done(); 54 | }); 55 | }); 56 | 57 | it('should not add \\n at empty to end of empty line', function (done) { 58 | testgen.load(p.join(__dirname, 'fixtures/empty.txt'), function (data) { 59 | 60 | assert.strictEqual(data.fixtures[0].first.text, 'a\n'); 61 | assert.strictEqual(data.fixtures[0].second.text, ''); 62 | 63 | done(); 64 | }); 65 | }); 66 | 67 | it('should scan dir', function () { 68 | var files = 0; 69 | 70 | testgen.load(p.join(__dirname, 'fixtures'), function () { 71 | files++; 72 | }); 73 | assert.strictEqual(files, 4); 74 | }); 75 | 76 | }); 77 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | env: 2 | node: true 3 | browser: false 4 | es6: false 5 | 6 | rules: 7 | accessor-pairs: 2 8 | array-bracket-spacing: [ 2, "always", { "singleValue": true, "objectsInArrays": true, "arraysInArrays": true } ] 9 | block-scoped-var: 2 10 | block-spacing: 2 11 | brace-style: [ 2, '1tbs', { allowSingleLine: true } ] 12 | # Postponed 13 | #callback-return: 2 14 | comma-dangle: 2 15 | comma-spacing: 2 16 | comma-style: 2 17 | computed-property-spacing: [ 2, never ] 18 | consistent-this: [ 2, self ] 19 | consistent-return: 2 20 | # ? change to multi 21 | curly: [ 2, 'multi-line' ] 22 | dot-notation: 2 23 | eol-last: 2 24 | eqeqeq: 2 25 | func-style: [ 2, declaration ] 26 | # Postponed 27 | #global-require: 2 28 | guard-for-in: 2 29 | handle-callback-err: 2 30 | 31 | indent: [ 2, 2, { VariableDeclarator: { var: 2, let: 2, const: 3 }, SwitchCase: 1 } ] 32 | 33 | # key-spacing: [ 2, { "align": "value" } ] 34 | keyword-spacing: 2 35 | linebreak-style: 2 36 | max-depth: [ 1, 6 ] 37 | max-nested-callbacks: [ 1, 4 ] 38 | # string can exceed 80 chars, but should not overflow github website :) 39 | max-len: [ 2, 120, 1000 ] 40 | new-cap: 2 41 | new-parens: 2 42 | # Postponed 43 | #newline-after-var: 2 44 | no-alert: 2 45 | no-array-constructor: 2 46 | no-bitwise: 2 47 | no-caller: 2 48 | #no-case-declarations: 2 49 | no-catch-shadow: 2 50 | no-cond-assign: 2 51 | no-console: 1 52 | no-constant-condition: 2 53 | #no-control-regex: 2 54 | no-debugger: 2 55 | no-delete-var: 2 56 | no-div-regex: 2 57 | no-dupe-args: 2 58 | no-dupe-keys: 2 59 | no-duplicate-case: 2 60 | no-else-return: 2 61 | # Tend to drop 62 | # no-empty: 1 63 | no-empty-character-class: 2 64 | no-empty-pattern: 2 65 | no-eq-null: 2 66 | no-eval: 2 67 | no-ex-assign: 2 68 | no-extend-native: 2 69 | no-extra-bind: 2 70 | no-extra-boolean-cast: 2 71 | no-extra-semi: 2 72 | no-fallthrough: 2 73 | no-floating-decimal: 2 74 | no-func-assign: 2 75 | # Postponed 76 | #no-implicit-coercion: [2, { "boolean": true, "number": true, "string": true } ] 77 | no-implied-eval: 2 78 | no-inner-declarations: 2 79 | no-invalid-regexp: 2 80 | no-irregular-whitespace: 2 81 | no-iterator: 2 82 | no-label-var: 2 83 | no-labels: 2 84 | no-lone-blocks: 2 85 | no-lonely-if: 2 86 | no-loop-func: 2 87 | no-mixed-requires: 2 88 | no-mixed-spaces-and-tabs: 2 89 | # Postponed 90 | #no-native-reassign: 2 91 | no-negated-in-lhs: 2 92 | # Postponed 93 | #no-nested-ternary: 2 94 | no-new: 2 95 | no-new-func: 2 96 | no-new-object: 2 97 | no-new-require: 2 98 | no-new-wrappers: 2 99 | no-obj-calls: 2 100 | no-octal: 2 101 | no-octal-escape: 2 102 | no-path-concat: 2 103 | no-proto: 2 104 | no-redeclare: 2 105 | # Postponed 106 | #no-regex-spaces: 2 107 | no-return-assign: 2 108 | no-self-compare: 2 109 | no-sequences: 2 110 | no-shadow: 2 111 | no-shadow-restricted-names: 2 112 | no-sparse-arrays: 2 113 | no-trailing-spaces: 2 114 | no-undef: 2 115 | no-undef-init: 2 116 | no-undefined: 2 117 | no-unexpected-multiline: 2 118 | no-unreachable: 2 119 | no-unused-expressions: 2 120 | no-unused-vars: 2 121 | no-use-before-define: 2 122 | no-void: 2 123 | no-with: 2 124 | object-curly-spacing: [ 2, always, { "objectsInObjects": true, "arraysInObjects": true } ] 125 | operator-assignment: 1 126 | # Postponed 127 | #operator-linebreak: [ 2, after ] 128 | semi: 2 129 | semi-spacing: 2 130 | space-before-function-paren: [ 2, { "anonymous": "always", "named": "never" } ] 131 | space-in-parens: [ 2, never ] 132 | space-infix-ops: 2 133 | space-unary-ops: 2 134 | # Postponed 135 | #spaced-comment: [ 1, always, { exceptions: [ '/', '=' ] } ] 136 | strict: [ 2, global ] 137 | quotes: [ 2, single, avoid-escape ] 138 | quote-props: [ 1, 'as-needed', { "keywords": true } ] 139 | radix: 2 140 | use-isnan: 2 141 | valid-typeof: 2 142 | yoda: [ 2, never, { "exceptRange": true } ] 143 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* global it, describe */ 2 | 'use strict'; 3 | 4 | var fs = require('fs'); 5 | var p = require('path'); 6 | 7 | var assign = require('object-assign'); 8 | var yaml = require('js-yaml'); 9 | 10 | 11 | function _class(obj) { return Object.prototype.toString.call(obj); } 12 | 13 | function isString(obj) { return _class(obj) === '[object String]'; } 14 | function isFunction(obj) { return _class(obj) === '[object Function]'; } 15 | function isArray(obj) { return _class(obj) === '[object Array]'; } 16 | 17 | 18 | function fixLF(str) { 19 | return str.length ? str + '\n' : str; 20 | } 21 | 22 | function parse(input, options) { 23 | var lines = input.split(/\r?\n/g), 24 | max = lines.length, 25 | min = 0, 26 | line = 0, 27 | fixture, i, l, currentSep, blockStart; 28 | 29 | var result = { 30 | fixtures: [] 31 | }; 32 | 33 | var sep = options.sep || [ '.' ]; 34 | 35 | // Try to parse meta 36 | if (/^-{3,}$/.test(lines[0] || '')) { 37 | line++; 38 | while (line < max && !/^-{3,}$/.test(lines[line])) { line++; } 39 | 40 | // If meta end found - extract range 41 | if (line < max) { 42 | result.meta = lines.slice(1, line).join('\n'); 43 | line++; 44 | min = line; 45 | 46 | } else { 47 | // if no meta closing - reset to start and try to parse data without meta 48 | line = 1; 49 | } 50 | } 51 | 52 | // Scan fixtures 53 | while (line < max) { 54 | if (sep.indexOf(lines[line]) < 0) { 55 | line++; 56 | continue; 57 | } 58 | 59 | currentSep = lines[line]; 60 | 61 | fixture = { 62 | type: currentSep, 63 | header: '', 64 | first: { 65 | text: '', 66 | range: [] 67 | }, 68 | second: { 69 | text: '', 70 | range: [] 71 | } 72 | }; 73 | 74 | line++; 75 | blockStart = line; 76 | 77 | // seek end of first block 78 | while (line < max && lines[line] !== currentSep) { line++; } 79 | if (line >= max) { break; } 80 | 81 | fixture.first.text = fixLF(lines.slice(blockStart, line).join('\n')); 82 | fixture.first.range.push(blockStart, line); 83 | line++; 84 | blockStart = line; 85 | 86 | // seek end of second block 87 | while (line < max && lines[line] !== currentSep) { line++; } 88 | if (line >= max) { break; } 89 | 90 | fixture.second.text = fixLF(lines.slice(blockStart, line).join('\n')); 91 | fixture.second.range.push(blockStart, line); 92 | line++; 93 | 94 | // Look back for header on 2 lines before texture blocks 95 | i = fixture.first.range[0] - 2; 96 | while (i >= Math.max(min, fixture.first.range[0] - 3)) { 97 | l = lines[i]; 98 | if (sep.indexOf(l) >= 0) { break; } 99 | if (l.trim().length) { 100 | fixture.header = l.trim(); 101 | break; 102 | } 103 | i--; 104 | } 105 | 106 | result.fixtures.push(fixture); 107 | } 108 | 109 | return (result.meta || result.fixtures.length) ? result : null; 110 | } 111 | 112 | 113 | // Read fixtures recursively, and run iterator on parsed content 114 | // 115 | // Options 116 | // 117 | // - sep (String|Array) - allowed fixture separator(s) 118 | // 119 | // Parsed data fields: 120 | // 121 | // - file (String): file name 122 | // - meta (Mixed): metadata from header, if exists 123 | // - fixtures 124 | // 125 | function load(path, options, iterator) { 126 | var input, parsed, 127 | stat = fs.statSync(path); 128 | 129 | if (isFunction(options)) { 130 | iterator = options; 131 | options = { sep: [ '.' ] }; 132 | } else if (isString(options)) { 133 | options = { sep: options.split('') }; 134 | } else if (isArray(options)) { 135 | options = { sep: options }; 136 | } 137 | 138 | if (stat.isFile()) { 139 | input = fs.readFileSync(path, 'utf8'); 140 | 141 | parsed = parse(input, options); 142 | 143 | if (!parsed) { return null; } 144 | 145 | parsed.file = path; 146 | try { 147 | parsed.meta = yaml.safeLoad(parsed.meta || ''); 148 | } catch (__) { 149 | parsed.meta = null; 150 | } 151 | 152 | if (iterator) { 153 | iterator(parsed); 154 | } 155 | return parsed; 156 | } 157 | 158 | var result, res; 159 | if (stat.isDirectory()) { 160 | result = []; 161 | 162 | fs.readdirSync(path).forEach(function (name) { 163 | res = load(p.join(path, name), options, iterator); 164 | if (Array.isArray(res)) { 165 | result = result.concat(res); 166 | } else if (res) { 167 | result.push(res); 168 | } 169 | }); 170 | 171 | return result; 172 | } 173 | 174 | // Silently other entries (symlinks and so on) 175 | return null; 176 | } 177 | 178 | 179 | function generate(path, options, md) { 180 | if (!md) { 181 | md = options; 182 | options = {}; 183 | } 184 | 185 | options = assign({}, options); 186 | options.assert = options.assert || require('chai').assert; 187 | 188 | load(path, options, function (data) { 189 | data.meta = data.meta || {}; 190 | 191 | var desc = data.meta.desc || p.relative(path, data.file); 192 | 193 | (data.meta.skip ? describe.skip : describe)(desc, function () { 194 | data.fixtures.forEach(function (fixture) { 195 | it(fixture.header && options.header ? fixture.header : 'line ' + (fixture.first.range[0] - 1), function () { 196 | options.assert.strictEqual(md.render(fixture.first.text), fixture.second.text); 197 | }); 198 | }); 199 | }); 200 | }); 201 | } 202 | 203 | module.exports = generate; 204 | module.exports.load = load; 205 | --------------------------------------------------------------------------------