├── README.md ├── SAMPLE-webpack.config.js ├── SAMPLE.babelrc ├── package.json ├── tapered-babel-plugin.js ├── tapered-webpack-plugin.js └── tapered.js-logo.jpg /README.md: -------------------------------------------------------------------------------- 1 | ![Alt tapered logo](https://image.ibb.co/eiJTxv/A0033738_order_Mock_03071107_2.jpg "Optional title") 2 | 3 | ## Synopsis 4 | Easy inline test writing. 5 | tapered uses **Babel** and **Webpack** to generate **Tape** test files from test code written inline in comments 6 | 7 | ## Installation 8 | npm install tapered.js 9 | 10 | ## Setup 11 | #### 1. babelrc 12 | 13 | Add the following lines to your .babelrc file. 14 | * "presets": ["env"], 15 | * "plugins": ["tapered.js/tapered-babel-plugin.js"] 16 | 17 | #### 2. webpack.config 18 | Add these requires to the top of your webpack.config file: 19 | * const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); 20 | * const tapered = require('tapered.js/tapered-webpack-plugin.js'); 21 | 22 | Add the following to your webpack's plugins array and specify the location of your test file e.g. _/tests/tape-test-sample.js_ 23 | ```javascript 24 | plugins : [ 25 | new tapered(), 26 | new webpack.optimize.UglifyJsPlugin({ 27 | extractComments: { 28 | condition: /dab/, 29 | filename: '_INSERT TEST FILE LOCATION HERE_' 30 | }, 31 | }), 32 | ], 33 | ``` 34 | #### 3. tapered-webpack-plugin.js 35 | Specify the _same_ desired test file location for the fields marked with _'INSERT TEST FILE LOCATION HERE'_ in the tapered.js folder inside your _node modules_ directory: **_/node_modules/tapered.js/tapered-webpack-plugin.js_** 36 | ```javascript 37 | const file = compilation.assets['INSERT DESIRED TEST FILE LOCATION HERE']; 38 | compilation.assets['INSERT DESIRED TEST FILE LOCATION HERE'] 39 | ``` 40 | ## Usage 41 | 42 | #### Quick Start 43 | ```javascript 44 | >>: _test name_ 45 | >>:a: _specify assertion_ 46 | ``` 47 | #### Simple Unit Test Writing 48 | ###### Let's write a test file for the code below: 49 | ```javascript 50 | const demo = {}; 51 | demo.add = function(a, b) { 52 | return a + b; 53 | } 54 | ``` 55 | ###### We write our tapered tests in _**block**_ comments 56 | ```javascript 57 | /* %tapered */ 58 | 59 | /* 60 | >>:add 61 | >>:a: demo.add(1, 2) equal 3 | should add numbers 62 | >>:a: demo.add(0, 1) notEqual 2 | should add numbers correctly 63 | */ 64 | ``` 65 | ###### This is transpiled to Tape code as follows: 66 | ```javascript 67 | const demo = require('/src/demo.js'); 68 | 69 | test('add', function (t) { 70 | t.equal(demo.add(1, 2), 3 , 'should add numbers'); 71 | t.notEqual(demo.add(0, 1), 2 , 'should add numbers correctly'); 72 | t.end(); 73 | }); 74 | ``` 75 | ## API Reference 76 | 77 | #### Key Reminders 78 | * **_1. All tapered code must be written inside of _**block**_ comments._** 79 | * **_2. Tests must always begin with a name._** 80 | 81 | Syntax | Function 82 | ------------ | ------------- 83 | ```javascript 84 | %tapered | requires the containing file // require('/src/demo.js'); 85 | >>: _name_ | define test name // test('name')... 86 | >>: _variables_ | define variables per specific test // let i = 0 87 | >>:a: _assertions_ | define assertions // demo.add(1, 2) equal 3 88 | %g | defines global variables accessible across the entire test file // let arr = [1, 2, 3] 89 | >>:x: | skips a test // test.skip('name')... 90 | >>:o: | tests only that test // test.only('name')... 91 | >>:p: | specifies how many assertions to run per test // t.plan('name') 92 | ``` 93 | 94 | #### Assertions 95 | ##### Components 96 | 1. Expression e.g. _demo.multiply(1,2)_ 97 | 2. Assertion e.g. _equal_ 98 | 3. Expected e.g. _2_ 99 | 4. Description/Message e.g. _should multiply numbers_ 100 | 101 | ##### Format 102 | ```javascript 103 | >>:a: Expression Assertion Expected | optional message 104 | 105 | >>:a: demo.multiply(1,2) equal 2 | should multiply numbers 106 | ``` 107 | 108 | #### Supported Assertions 109 | 110 | Assertion | Function 111 | ---------|------- 112 | equal | asserts equality 113 | notEqual | asserts inequality 114 | deepEqual | asserts deep equality - use when comparing objects 115 | notDeepEqual | asserts deep inequality - use when comparing objects 116 | -------------------------------------------------------------------------------- /SAMPLE-webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack'); 3 | const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); 4 | const tapered = require('tapered.js/tapered-webpack-plugin.js'); 5 | 6 | module.exports = { 7 | entry: ['./src/index.js', './index.js'], 8 | output: { 9 | path: path.join(__dirname, '/client'), 10 | filename: 'bundle.js' 11 | }, 12 | module: { 13 | rules: [ 14 | { test: /jsx?/, 15 | exclude: /node_modules/, 16 | use: 'babel-loader', 17 | }, 18 | { 19 | test: /scss$/, 20 | exclude: /node_modules/, 21 | use: ['style-loader', 'css-loader', 'sass-loader'] 22 | } 23 | ] 24 | }, 25 | plugins : [ 26 | new tapered(), 27 | new webpack.optimize.UglifyJsPlugin({ 28 | extractComments: { 29 | condition: /ß∂dNß0j1/, 30 | filename: 'INSERT TEST FILE LOCATION HERE' 31 | }, 32 | }), 33 | ], 34 | } 35 | -------------------------------------------------------------------------------- /SAMPLE.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "react"], 3 | "plugins": ["tapered.js/tapered-babel-plugin.js"] 4 | } 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tapered.js", 3 | "version": "1.0.0", 4 | "description": "Easy inline test-writing using Tape", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "badNboji", 10 | "license": "MIT", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/badNboji/tapered-js" 14 | }, 15 | "dependencies": { 16 | "babel-cli": "^6.24.1", 17 | "babel-core": "^6.25.0", 18 | "babel-loader": "^7.1.0", 19 | "babel-preset-env": "^1.5.2", 20 | "tape": "^4.6.3", 21 | "webpack": "^3.0.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tapered-babel-plugin.js: -------------------------------------------------------------------------------- 1 | module.exports = function({types: t}) { 2 | return { 3 | visitor: { 4 | Program(path, state) { 5 | // if there are comments in the program file 6 | if (path.parent.comments.length) { 7 | const comments = path.parent.comments 8 | // loop through the comments 9 | for (let i = 0; i < comments.length; i += 1) { 10 | // if a comment requires %tapered then add the containing file as a dependency 11 | const reqFileName = "const " + state.file.opts.sourceMapTarget.slice(0, state.file.opts.sourceMapTarget.length - 3) + " = require('" + state.file.opts.filename + "');"; 12 | if (comments[i].value.includes("%tapered")) { 13 | comments[i].value = " ß∂dNß0j1" + reqFileName; 14 | } 15 | if (comments[i].value.includes("%g")) { 16 | let globalStart = comments[i].value.indexOf("%g"); 17 | comments[i].value = " ß∂dNß0j1" + comments[i].value.slice(globalStart + 2).replace(/^[ ]+|[ ]+$/g, ''); 18 | } 19 | // ---------------------------------------------------------------------------- 20 | // SECTION INCLUDES NAME/DESCRIPTION - ASSERTION AND VARIABLES 21 | // Split on >>: 22 | if (comments[i].value.includes('>>:')) { 23 | let currcomments = comments[i].value.split(">>:"); 24 | // currcomments[0] is empty 25 | // if currcomments[1] = test name/description and is required 26 | // if currcomments[1] contains an x: skip the test 27 | let test = ' ß∂dNß0j1test'; 28 | let description = currcomments[1].replace(/\r\n/, "\n").split(/\n/)[0].replace(/^[ ]+|[ ]+$/g, ''); 29 | if (description[0] === 'x' && description[1] === ':') { 30 | description = description.slice(2).replace(/^[ ]+|[ ]+$/g, ''); 31 | test = ' ß∂dNß0j1test.skip'; 32 | }; 33 | if (description[0] === 'o' && description[1] === ':') { 34 | description = description.slice(2).replace(/^[ ]+|[ ]+$/g, ''); 35 | test = ' ß∂dNß0j1test.only'; 36 | }; 37 | let actual; 38 | let expression; 39 | let expected; 40 | let errMessage; 41 | let resultOfAssertion = ""; 42 | let assertion; 43 | let startIndExpression; 44 | let expressionEndPoint; 45 | let argumentLength = 0; 46 | let variables = ""; 47 | let finalCommentsTranspiled = ""; 48 | let endTest = "\t" + 49 | "t.end();" + 50 | "\n"; 51 | // Helper function that splits the ASSERTION: actual, expected, and expression 52 | function assertions(string) { 53 | let argumentSplit = string.split("|"); 54 | let threeArgExpression = [ 55 | "equal", 56 | "notEqual", 57 | "deepEqual", 58 | "notDeepEqual", 59 | "deepLooseEqual", 60 | "notDeepLooseEqual", 61 | "throws", 62 | "doesNotThrow", 63 | "same", 64 | "notSame", 65 | "strictSame", 66 | "strictNotSame" 67 | ]; 68 | let twoArgExpression = ["ok", "notOk", "error"]; 69 | // Find the expression start index and end index 70 | for (let three = 0; three < threeArgExpression.length; three++) { 71 | if (argumentSplit[0].indexOf(threeArgExpression[three]) !== -1) { 72 | startIndExpression = argumentSplit[0].indexOf(threeArgExpression[three]); 73 | expressionEndPoint = startIndExpression + threeArgExpression[three].length; 74 | argumentLength = 3; 75 | } 76 | } 77 | for (let two = 0; two < twoArgExpression.length; two++) { 78 | if (argumentSplit[0].indexOf(twoArgExpression[two]) !== -1) { 79 | startIndExpression = argumentSplit[0].indexOf(twoArgExpression[two]); 80 | expressionEndPoint = startIndExpression + twoArgExpression[two].length; 81 | argumentLength = 2; 82 | } 83 | } 84 | // If assertion contains an error message 85 | if (argumentLength === 3) { 86 | if (argumentSplit.length > 1) { 87 | 88 | actual = argumentSplit[0].slice(0, startIndExpression).replace(/^[ ]+|[ ]+$/g, ''); 89 | expression = argumentSplit[0].slice(startIndExpression, expressionEndPoint).replace(/^[ ]+|[ ]+$/g, ''); 90 | expected = argumentSplit[0].slice(expressionEndPoint).replace(/\r\n/, "\n").split(/\n/)[0]; 91 | let message = argumentSplit[1].replace(/\s*[\r\n]+\s*/g, "\n").split(/\n/)[0].replace(/^[ ]+|[ ]+$/g, ''); 92 | errMessage = "'" + message + "'"; 93 | } 94 | // If assertion does not contain an error message 95 | if (argumentSplit.length < 2) { 96 | 97 | actual = argumentSplit[0].slice(0, startIndExpression).replace(/^[ ]+|[ ]+$/g, ''); 98 | expression = argumentSplit[0].slice(startIndExpression, expressionEndPoint).replace(/^[ ]+|[ ]+$/g, ''); 99 | expected = argumentSplit[0].slice(expressionEndPoint).replace(/\r\n/, "\n").split(/\n/)[0]; 100 | errMessage = "'" + 101 | "Error: " + description + "'"; 102 | } 103 | resultOfAssertion = "\t" + 104 | "t." + expression + "(" + actual + ", " + expected + ", " + errMessage + ");" + "\n"; 105 | return resultOfAssertion; 106 | } 107 | if (argumentLength === 2) { 108 | 109 | if (argumentSplit.length > 1) { 110 | 111 | expression = argumentSplit[0].slice(startIndExpression, expressionEndPoint).replace(/^[ ]+|[ ]+$/g, ''); 112 | expected = argumentSplit[0].slice(expressionEndPoint).replace(/\r\n/, "\n").split(/\n/)[0]; 113 | let message = argumentSplit[1].replace(/\s*[\r\n]+\s*/g, "\n").split(/\n/)[0].replace(/^[ ]+|[ ]+$/g, ''); 114 | errMessage = "'" + message + "'"; 115 | } 116 | // If assertion does not contain an error message 117 | if (argumentSplit.length < 2) { 118 | 119 | expression = argumentSplit[0].slice(startIndExpression, expressionEndPoint).replace(/^[ ]+|[ ]+$/g, ''); 120 | expected = argumentSplit[0].slice(expressionEndPoint).replace(/\r\n/, "\n").split(/\n/)[0]; 121 | errMessage = "'" + 122 | "Error: " + description + "'"; 123 | } 124 | resultOfAssertion = "\t" + 125 | "t." + expression + "(" + expected + ", " + errMessage + ");" + "\n"; 126 | return resultOfAssertion; 127 | } 128 | } 129 | for (let index = 2; index < currcomments.length; index++) { 130 | // if comments[index] is an assertion 131 | if (currcomments[index].indexOf("a:") !== -1 && currcomments[index][0] === 'a' && currcomments[index][1] === ':') { 132 | assertion = assertions(currcomments[index].slice(2).replace(/^[ ]+|[ ]+$/g, '')); 133 | finalCommentsTranspiled += assertion; 134 | } 135 | // if currcomments[index] is a variable (optional) 136 | if (currcomments[index][0] !== 'a' && currcomments[index][1] !== ':' && currcomments[index] !== undefined && /\S/.test(currcomments[index])) { 137 | 138 | let splitVariable = currcomments[index].replace(/\r\n/, "\n").split(/\n/); 139 | for (let eachVar = 0; eachVar < splitVariable.length; eachVar++) { 140 | variables += "\t" + splitVariable[eachVar].replace(/^[ ]+|[ ]+$/g, '') + "\n"; 141 | } 142 | finalCommentsTranspiled += variables; 143 | variables = ""; 144 | } 145 | // if curcomments[index] is ending in plan 146 | if (currcomments[index].indexOf("p:") !== -1 && currcomments[index][0] === 'p' && currcomments[index][1] === ':') { 147 | // let plan = "\t" + currcomments[index].slice(2).replace(/^[ ]+|[ ]+$/g, ''); 148 | let planInput = currcomments[index].slice(2).replace(/\s/g, ''); 149 | endTest = ''; 150 | finalCommentsTranspiled += `\tt.plan(${planInput});\n` 151 | } 152 | } 153 | comments[i].value = test + "('" + description + "', function (t) {" + "\n" + finalCommentsTranspiled + endTest + "});"; 154 | } 155 | } 156 | } 157 | } 158 | } 159 | }; 160 | } 161 | -------------------------------------------------------------------------------- /tapered-webpack-plugin.js: -------------------------------------------------------------------------------- 1 | function tapered() {} 2 | 3 | tapered.prototype.apply = function(compiler) { 4 | compiler.plugin('emit', function(compilation, callback) { 5 | function unComment() { 6 | const file = compilation.assets['INSERT TEST FILE LOCATION HERE']; 7 | if (file === undefined) { 8 | setTimeout(function() { 9 | unComment 10 | }, 1000); 11 | } else if (file) { 12 | compilation.assets['INSERT TEST FILE LOCATION HERE'] = { 13 | source: function() { 14 | return "const test = require('tape')" + "\n" + file.source().replace(/(\/\*\ ß∂dNß0j1Tape)|(\/\*\ ß∂dNß0j1)|(\*\/)/g, ''); 15 | }, 16 | size: function() { 17 | return file.source().length; 18 | }, 19 | }; 20 | } 21 | } 22 | callback(); 23 | unComment(); 24 | }); 25 | }; 26 | 27 | module.exports = tapered; 28 | -------------------------------------------------------------------------------- /tapered.js-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badNboji/tapered-js/4959523c893779241476c43da02144b6e2635fff/tapered.js-logo.jpg --------------------------------------------------------------------------------