From 91e5942797ff1bc2e5ccbfdcc1f585ecc8327467 Mon Sep 17 00:00:00 2001 From: The Jared Wilcurt Date: Sun, 20 Dec 2020 09:41:34 -0500 Subject: [PATCH 01/21] CSS Parser --- src/css-parser.js | 37 ++++++++++++++++++++++++++++++++++++ tests/src/css-parser.test.js | 10 ++++++++++ 2 files changed, 47 insertions(+) diff --git a/src/css-parser.js b/src/css-parser.js index b7542d0..d1dc4ef 100644 --- a/src/css-parser.js +++ b/src/css-parser.js @@ -15,6 +15,43 @@ const cssParser = function (options, input) { }; const parsed = css.parse(input, parseOptions); + /* + input = '.test { color: #F00 }'; + parsed = { + type: 'stylesheet', + stylesheet: { + rules: [ + { + type: 'rule', + selectors: [ + [ + { + action: 'element', + ignoreCase: false, + name: 'class', + namespace: null, + original: '.test', + type: 'attribute', + value: 'test' + } + ] + ], + declarations: [ + { + position: {...}, + property: 'color', + type: 'declaration', + value: '#F00' + } + ], + position: {...} + } + ], + source: undefined, + parsingErrors: [] + } + } + */ if (parsed && parsed.stylesheet && parsed.stylesheet.rules) { parsed.stylesheet.rules.forEach(function (rule) { let parsedSelectors = selectorParse(rule.selectors.join(',')); diff --git a/tests/src/css-parser.test.js b/tests/src/css-parser.test.js index 4a2bf41..626b0ba 100644 --- a/tests/src/css-parser.test.js +++ b/tests/src/css-parser.test.js @@ -13,6 +13,16 @@ describe('CSS parser', () => { expect(cssParser(options, undefined)) .toEqual(undefined); }); + + test('Empty string', () => { + expect(cssParser(options, '')) + .toEqual(undefined); + }); + + test('HTML', () => { + expect(cssParser(options, '

Bad

').stylesheet.parsingErrors.length) + .toEqual(1); + }); }); describe('Parses string to AST', () => { From 09b3469212e9d4c27895a89f5167c7c5f313aeb7 Mon Sep 17 00:00:00 2001 From: The Jared Wilcurt Date: Sun, 20 Dec 2020 11:43:51 -0500 Subject: [PATCH 02/21] Class Encoding --- src/class-encoding.js | 29 +++++- src/css.js | 4 +- tests/src/css-encoding.test.js | 164 ++++++++++++++++++++++++++++++++ tests/src/css-stringify.test.js | 1 - 4 files changed, 193 insertions(+), 5 deletions(-) create mode 100644 tests/src/css-encoding.test.js diff --git a/src/class-encoding.js b/src/class-encoding.js index 7c58562..b6cd71e 100644 --- a/src/class-encoding.js +++ b/src/class-encoding.js @@ -1,3 +1,5 @@ +const helpers = require('./helpers.js'); + // Initially I thought this too verbose, but there is literally no limit on class lengths other than the machine's memory/CPU. // Firefox actually let me create and reference a classname with 100,000,000 characters. It was slow, but it worked fine. const propertyValueEncodingMap = { @@ -68,10 +70,33 @@ const prefix = 'rp__'; /** * Encodes propter/value pairs as valid, decodable classnames. * + * @param {object} options User's passed in options, containing verbose/customLoger * @param {object} declaration Contains the Property and Value strings * @return {string} A classname starting with . and a prefix */ -function encodeClassName (declaration) { +function encodeClassName (options, declaration) { + if (!declaration || declaration.property === undefined || declaration.value === undefined) { + let message = [ + 'A rule declaration was missing details,', + 'such as property or value.', + 'This may result in a classname like', + '.rp__width__--COLONundefined,', + '.rp__undefined__--COLON100px,', + 'or', + '.rp__undefined__--COLONundefined.', + 'If there are multiples of these,', + 'they may replace the previous.', + 'Please report this error to', + 'github.com/red-perfume/red-perfume/issues', + 'because I have no idea how to', + 'reproduce it with actual CSS input.', + 'This was just meant for a safety check.', + 'Honestly, if you actually got this', + 'error, I\'m kind of impressed.' + ].join(' '); + helpers.throwError(options, message); + } + declaration = declaration || {}; let newName = declaration.property + ':' + declaration.value; let nameArray = newName.split(''); let encoded = nameArray.map(function (character) { @@ -84,4 +109,6 @@ function encodeClassName (declaration) { return '.' + prefix + encoded.join(''); } +// This is exported out too for a unit test +encodeClassName.propertyValueEncodingMap = propertyValueEncodingMap; module.exports = encodeClassName; diff --git a/src/css.js b/src/css.js index 7eccd6e..e12da58 100644 --- a/src/css.js +++ b/src/css.js @@ -95,7 +95,7 @@ const css = function (options, input, uglify) { /* An encoded class name look like: .rp__padding__--COLON10px */ - let encodedClassName = encodeClassName(declaration); + let encodedClassName = encodeClassName(options, declaration); /* A selector looks like: [{ @@ -115,8 +115,6 @@ const css = function (options, input, uglify) { classMap[originalSelectorName].push(encodedClassName); }); - - newRules[encodedClassName] = { type: 'rule', selectors: [[encodedClassName]], diff --git a/tests/src/css-encoding.test.js b/tests/src/css-encoding.test.js new file mode 100644 index 0000000..9eb5c07 --- /dev/null +++ b/tests/src/css-encoding.test.js @@ -0,0 +1,164 @@ +const classEncoding = require('@/class-encoding.js'); + +describe('Class encoding', () => { + let options; + const error = 'A rule declaration was missing details, such as ' + + 'property or value. This may result in a classname like ' + + '.rp__width__--COLONundefined, .rp__undefined__--COLON100px, or ' + + '.rp__undefined__--COLONundefined. If there are multiples of ' + + 'these, they may replace the previous. Please report this error ' + + 'to github.com/red-perfume/red-perfume/issues because I have no ' + + 'idea how to reproduce it with actual CSS input. This was just ' + + 'meant for a safety check. Honestly, if you actually got this ' + + 'error, I\'m kind of impressed.'; + + beforeEach(() => { + options = { + verbose: true, + customLogger: jest.fn() + }; + }); + + describe('Bad inputs', () => { + test('Empty', () => { + expect(classEncoding(options)) + .toEqual('.rp__undefined__--COLONundefined'); + + expect(options.customLogger) + .toHaveBeenCalledWith(error, undefined); + }); + + test('Array', () => { + expect(classEncoding(options, [])) + .toEqual('.rp__undefined__--COLONundefined'); + + expect(options.customLogger) + .toHaveBeenCalledWith(error, undefined); + }); + + test('Number', () => { + expect(classEncoding(options, 4)) + .toEqual('.rp__undefined__--COLONundefined'); + + expect(options.customLogger) + .toHaveBeenCalledWith(error, undefined); + }); + + test('Empty object', () => { + expect(classEncoding(options, {})) + .toEqual('.rp__undefined__--COLONundefined'); + + expect(options.customLogger) + .toHaveBeenCalledWith(error, undefined); + }); + + test('Declaration without property', () => { + expect(classEncoding(options, { value: '1px' })) + .toEqual('.rp__undefined__--COLON1px'); + + expect(options.customLogger) + .toHaveBeenCalledWith(error, undefined); + }); + + test('Declaration without value', () => { + expect(classEncoding(options, { property: 'width' })) + .toEqual('.rp__width__--COLONundefined'); + + expect(options.customLogger) + .toHaveBeenCalledWith(error, undefined); + }); + }); + + describe('Encode', () => { + test('Encode hex color', () => { + const declaration = { + property: 'background', + value: '#F00' + }; + + expect(classEncoding(options, declaration)) + .toEqual('.rp__background__--COLON__--OCTOTHORPF00'); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + + test('Encode all special characters', () => { + const allSpecialCharacters = '&*@\\^¢>})]:,©¤°÷$."=!/`½µ#<{([¶%|+±£¼?®;\'¾~¥ '; + let allSpecialEncodings = [ + '__--AMPERSAND', + '__--ASTERISK', + '__--ATSIGN', + '__--BACKSLASH', + '__--CARET', + '__--CENT', + '__--CLOSEANGLEBRACKET', + '__--CLOSECURLYBRACE', + '__--CLOSEPAREN', + '__--CLOSESQUAREBRACKET', + '__--COLON', + '__--COMMA', + '__--COPYRIGHT', + '__--CURRENCY', + '__--DEGREE', + '__--DIVIDE', + '__--DOLARSIGN', + '__--DOT', + '__--DOUBLEQUOTE', + '__--EQUAL', + '__--EXCLAMATION', + '__--FORWARDSLASH', + '__--GRAVE', + '__--HALF', + '__--MU', + '__--OCTOTHORP', + '__--OPENANGLEBRACKET', + '__--OPENCURLYBRACE', + '__--OPENPAREN', + '__--OPENSQUAREBRACKET', + '__--PARAGRAPH', + '__--PERCENT', + '__--PIPE', + '__--PLUS', + '__--PLUSMINUS', + '__--POUNDSTERLING', + '__--QUARTER', + '__--QUESTIONMARK', + '__--REGISTERED', + '__--SEMICOLON', + '__--SINGLEQUOTE', + '__--THREEQUARTERS', + '__--TILDE', + '__--YENYUAN', + '_____-' + ]; + + const declaration = { + property: 'content', + value: allSpecialCharacters + }; + + expect(Object.keys(classEncoding.propertyValueEncodingMap).join('')) + .toEqual(allSpecialCharacters); + + expect(classEncoding(options, declaration)) + .toEqual('.rp__content__--COLON' + allSpecialEncodings.join('')); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + + test('Encode unicode characters', () => { + const declaration = { + property: 'content', + value: '🌕🌗🌑' + }; + + expect(classEncoding(options, declaration)) + .toEqual('.rp__content__--COLON__--U55356__--U57109__--U55356__--U57111__--U55356__--U57105'); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + }); +}); diff --git a/tests/src/css-stringify.test.js b/tests/src/css-stringify.test.js index 7b2f138..bd4d1e7 100644 --- a/tests/src/css-stringify.test.js +++ b/tests/src/css-stringify.test.js @@ -42,7 +42,6 @@ describe('CSS stringify', () => { .toEqual(''); }); - test('1 rule', () => { const AST = { type: 'stylesheet', From cb3559f4766980502633403445f050321eee6bee Mon Sep 17 00:00:00 2001 From: The Jared Wilcurt <4629794+TheJaredWilcurt@users.noreply.github.com> Date: Sun, 20 Dec 2020 12:49:44 -0500 Subject: [PATCH 03/21] rename class-encoding => css-class-encoding --- src/{class-encoding.js => css-class-encoding.js} | 0 src/css.js | 2 +- tests/src/{css-encoding.test.js => css-class-encoding.test.js} | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/{class-encoding.js => css-class-encoding.js} (100%) rename tests/src/{css-encoding.test.js => css-class-encoding.test.js} (98%) diff --git a/src/class-encoding.js b/src/css-class-encoding.js similarity index 100% rename from src/class-encoding.js rename to src/css-class-encoding.js diff --git a/src/css.js b/src/css.js index e12da58..9a4b3ae 100644 --- a/src/css.js +++ b/src/css.js @@ -1,7 +1,7 @@ const cssParser = require('./css-parser.js'); const cssStringify = require('./css-stringify.js'); const cssUglifier = require('./css-uglifier.js'); -const encodeClassName = require('./class-encoding.js'); +const encodeClassName = require('./css-class-encoding.js'); const css = function (options, input, uglify) { const parsed = cssParser(options, input); diff --git a/tests/src/css-encoding.test.js b/tests/src/css-class-encoding.test.js similarity index 98% rename from tests/src/css-encoding.test.js rename to tests/src/css-class-encoding.test.js index 9eb5c07..46de0e7 100644 --- a/tests/src/css-encoding.test.js +++ b/tests/src/css-class-encoding.test.js @@ -1,4 +1,4 @@ -const classEncoding = require('@/class-encoding.js'); +const classEncoding = require('@/css-class-encoding.js'); describe('Class encoding', () => { let options; From 58b2b27cbb9879fd3456d5304cd84512463f7cf3 Mon Sep 17 00:00:00 2001 From: The Jared Wilcurt <4629794+TheJaredWilcurt@users.noreply.github.com> Date: Sun, 20 Dec 2020 12:50:01 -0500 Subject: [PATCH 04/21] Check for logs in CSS Parser --- tests/src/css-parser.test.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/tests/src/css-parser.test.js b/tests/src/css-parser.test.js index 626b0ba..47e345d 100644 --- a/tests/src/css-parser.test.js +++ b/tests/src/css-parser.test.js @@ -1,27 +1,38 @@ -const validator = require('@/validator.js'); const cssParser = require('@/css-parser.js'); describe('CSS parser', () => { let options; beforeEach(() => { - options = validator.validateOptions({ verbose: false }); + options = { + verbose: false, + customLogger: jest.fn() + }; }); describe('Bad inputs', () => { test('Empty', () => { expect(cssParser(options, undefined)) .toEqual(undefined); + + expect(options.customLogger) + .not.toHaveBeenCalled(); }); test('Empty string', () => { expect(cssParser(options, '')) .toEqual(undefined); + + expect(options.customLogger) + .not.toHaveBeenCalled(); }); test('HTML', () => { expect(cssParser(options, '

Bad

').stylesheet.parsingErrors.length) .toEqual(1); + + expect(options.customLogger) + .not.toHaveBeenCalled(); }); }); @@ -82,6 +93,9 @@ describe('CSS parser', () => { parsingErrors: [] } }); + + expect(options.customLogger) + .not.toHaveBeenCalled(); }); }); }); From fc848dd606a488f651138fca913a880b6bc40d39 Mon Sep 17 00:00:00 2001 From: The Jared Wilcurt <4629794+TheJaredWilcurt@users.noreply.github.com> Date: Sun, 20 Dec 2020 12:50:09 -0500 Subject: [PATCH 05/21] CSS Uglifier --- src/css-uglifier.js | 12 +++---- tests/src/css-uglifier.test.js | 65 ++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 tests/src/css-uglifier.test.js diff --git a/src/css-uglifier.js b/src/css-uglifier.js index da4110d..90932dc 100644 --- a/src/css-uglifier.js +++ b/src/css-uglifier.js @@ -1,12 +1,11 @@ /** - * Increment the Uglifier index. Skips known bad - * numbers when base 36 encoded. + * Increment the Uglifier index if it contains a known bad + * value when base 36 encoded. * * @param {number} uglifierIndex Initial value - * @return {number} Incremented value + * @return {number} Incremented or original value (if good) */ -function incrementUglifier (uglifierIndex) { - uglifierIndex = uglifierIndex + 1; +function incrementIfContainsBad (uglifierIndex) { let knownBad = [ 'ad' // adblockers may hide these elements ]; @@ -37,10 +36,11 @@ function cssUglifier (uglifierIndex) { uglifierIndex = 0; } uglifierIndex = Math.round(uglifierIndex); + uglifierIndex = incrementIfContainsBad(uglifierIndex); return { name: '.rp__' + uglifierIndex.toString(36), - index: incrementUglifier(uglifierIndex) + index: uglifierIndex + 1 }; } diff --git a/tests/src/css-uglifier.test.js b/tests/src/css-uglifier.test.js new file mode 100644 index 0000000..e83950e --- /dev/null +++ b/tests/src/css-uglifier.test.js @@ -0,0 +1,65 @@ +const cssUglifier = require('@/css-uglifier.js'); + +describe('Class encoding', () => { + describe('Bad inputs', () => { + test('Empty', () => { + expect(cssUglifier()) + .toEqual({ + name: '.rp__0', + index: 1 + }); + }); + + test('Array', () => { + expect(cssUglifier([])) + .toEqual({ + name: '.rp__0', + index: 1 + }); + }); + + test('String', () => { + expect(cssUglifier('4')) + .toEqual({ + name: '.rp__0', + index: 1 + }); + }); + + test('Empty object', () => { + expect(cssUglifier({})) + .toEqual({ + name: '.rp__0', + index: 1 + }); + }); + }); + + describe('Uglify', () => { + test('Specific index', () => { + expect(cssUglifier(4)) + .toEqual({ + name: '.rp__4', + index: 5 + }); + }); + + test('Base 36 encoding', () => { + // parseInt('z', 36) = 35 + expect(cssUglifier(35)) + .toEqual({ + name: '.rp__z', + index: 36 // 10 + }); + }); + + test('Skip known bad', () => { + // parseInt('ad0', 36) = 13428 + expect(cssUglifier(13428)) + .toEqual({ + name: '.rp__ae0', + index: 13465 // ae1 + }); + }); + }); +}); From a2d02c32f38cb85ca12bb2323385ed1bb9c23dea Mon Sep 17 00:00:00 2001 From: The Jared Wilcurt <4629794+TheJaredWilcurt@users.noreply.github.com> Date: Sun, 20 Dec 2020 16:10:52 -0500 Subject: [PATCH 06/21] CSS Test --- src/css.js | 18 ++++++++- tests/src/css.test.js | 88 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 tests/src/css.test.js diff --git a/src/css.js b/src/css.js index 9a4b3ae..13968c6 100644 --- a/src/css.js +++ b/src/css.js @@ -2,9 +2,25 @@ const cssParser = require('./css-parser.js'); const cssStringify = require('./css-stringify.js'); const cssUglifier = require('./css-uglifier.js'); const encodeClassName = require('./css-class-encoding.js'); +const helpers = require('./helpers.js'); const css = function (options, input, uglify) { - const parsed = cssParser(options, input); + options = options || {}; + input = input || ''; + uglify = uglify || false; + let parsed; + try { + parsed = cssParser(options, input); + } catch (err) { + helpers.throwError(options, 'Error parsing CSS', err); + } + if (!parsed) { + helpers.throwError(options, 'Error parsing CSS', input); + return { + classMap: {}, + output: '' + }; + } const output = { type: 'stylesheet', diff --git a/tests/src/css.test.js b/tests/src/css.test.js new file mode 100644 index 0000000..b8b8715 --- /dev/null +++ b/tests/src/css.test.js @@ -0,0 +1,88 @@ +const css = require('@/css.js'); + +describe('CSS', () => { + let options; + const errorResponse = { + classMap: {}, + output: '' + }; + + beforeEach(() => { + options = { + verbose: true, + customLogger: jest.fn() + }; + }); + + describe('Bad inputs', () => { + test('Empty', () => { + expect(css()) + .toEqual(errorResponse); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + + test('Just options', () => { + expect(css(options)) + .toEqual(errorResponse); + + expect(options.customLogger) + .toHaveBeenCalledWith('Error parsing CSS', ''); + }); + + test('Options, empty string', () => { + expect(css(options, '')) + .toEqual(errorResponse); + + expect(options.customLogger) + .toHaveBeenCalledWith('Error parsing CSS', ''); + }); + + test('Options, HTML', () => { + expect(css(options, '

Bad

')) + .toEqual(errorResponse); + + let firstError = options.customLogger.mock.calls[0]; + let secondError = options.customLogger.mock.calls[1]; + + expect(JSON.stringify(firstError)) + .toEqual('["Error parsing CSS",{"reason":"missing \'{\'","line":1,"column":13,"source":""}]'); + + expect(secondError) + .toEqual(['Error parsing CSS', '

Bad

']); + }); + }); + + describe('Process CSS', () => { + test('One rule', () => { + expect(css(options, '.test { background: #F00; }', false)) + .toEqual({ + classMap: { + '.test': [ + '.rp__background__--COLON__--OCTOTHORPF00' + ] + }, + output: '.rp__background__--COLON__--OCTOTHORPF00 {\n background: #F00;\n}' + }); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + + test('One rule uglified', () => { + expect(css(options, '.test { background: #F00; }', true)) + .toEqual({ + classMap: { + '.test': [ + '.rp__0' + ] + }, + output: '.rp__0 {\n background: #F00;\n}' + }); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + }); +}); From 54e0673c06d06c3eb3e80ead11762c01c824f371 Mon Sep 17 00:00:00 2001 From: The Jared Wilcurt <4629794+TheJaredWilcurt@users.noreply.github.com> Date: Tue, 22 Dec 2020 10:30:43 -0500 Subject: [PATCH 07/21] HTML --- index.js | 2 +- src/html.js | 26 +++++++- tests/src/html.test.js | 146 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+), 3 deletions(-) create mode 100644 tests/src/html.test.js diff --git a/index.js b/index.js index b9bc314..706be56 100644 --- a/index.js +++ b/index.js @@ -22,7 +22,7 @@ const redPerfume = { task.markup.forEach((item) => { let processedMarkup; if (item.data) { - processedMarkup = html(item.data, processedStyles.classMap); + processedMarkup = html(options, item.data, processedStyles.classMap); } if (item.result) { item.result(processedMarkup, undefined); diff --git a/src/html.js b/src/html.js index 8947c15..d9f04d0 100644 --- a/src/html.js +++ b/src/html.js @@ -1,5 +1,7 @@ const parse5 = require('parse5'); +const helpers = require('./helpers.js'); + /** * Helper function used when you want to console.log(JSON.stringify(document)). * @@ -28,7 +30,21 @@ function cleanDocument (document) { } cleanDocument({}); -const html = function (input, classMap) { +/** + * Parse an HTML string. + * Replace the original classnames with the atomized versions. + * Reserialize HTML to string. + * + * @param {object} options Options object from user containing verbose/customLogger + * @param {string} input String of valid HTML + * @param {object} classMap Map generated by css.js containing class names as the key with array of atomized style class name strings as the value + * @return {string} String of HTML with the class names replaced + */ +const html = function (options, input, classMap) { + options = options || {}; + input = input || ''; + classMap = classMap || {}; + // String => Object const document = parse5.parse(input); @@ -92,7 +108,13 @@ const html = function (input, classMap) { }); // Object => string - return parse5.serialize(document); + let markup = parse5.serialize(document); + + if (!markup || markup === '') { + helpers.throwError(options, 'Error parsing HTML', (markup || document)); + } + + return markup; }; module.exports = html; diff --git a/tests/src/html.test.js b/tests/src/html.test.js new file mode 100644 index 0000000..e5cd2e6 --- /dev/null +++ b/tests/src/html.test.js @@ -0,0 +1,146 @@ +const html = require('@/html.js'); + +describe('HTML', () => { + let options; + const errorResponse = ''; + // const errorDocument = { + // childNodes: [], + // nodeName: '#document-fragment' + // }; + + beforeEach(() => { + options = { + verbose: true, + customLogger: jest.fn() + }; + }); + + describe('Bad inputs', () => { + test('Empty', () => { + expect(html()) + .toEqual(errorResponse); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + + test('Just options', () => { + expect(html(options)) + .toEqual(errorResponse); + + expect(options.customLogger) + .toHaveBeenCalledWith('Error parsing HTML', errorResponse); + }); + + test('Options, empty string', () => { + expect(html(options, '')) + .toEqual(errorResponse); + + expect(options.customLogger) + .toHaveBeenCalledWith('Error parsing HTML', errorResponse); + }); + + }); + + describe('Process HTML', () => { + test('Half comment', () => { + expect(html(options, '>'); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + + test('One rule', () => { + let classMap = { + '.test': [ + '.rp__background__--COLON__--OCTOTHORPF00' + ] + }; + + expect(html(options, '

Good

', classMap)) + .toEqual('

Good

'); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + + test('One rule uglified', () => { + let classMap = { + '.test': [ + '.rp__0' + ] + }; + + expect(html(options, '

Good

', classMap)) + .toEqual('

Good

'); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + + test('Two rules, two properties', () => { + let classMap = { + '.test': [ + '.rp__background__--COLON__--OCTOTHORPF00', + '.rp__width__--COLON100px' + ], + '.example': [ + '.rp__color__--COLON__--blue', + '.rp__padding__--COLON__--20px' + ] + }; + + expect(html(options, '

Good

', classMap)) + .toEqual([ + '', + '', + '', + '', + '', + '

', + 'Good', + '

', + '', + '' + ].join('')); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + + test('Two rules, two properties uglified', () => { + let classMap = { + '.test': [ + '.rp__0', + '.rp__1' + ], + '.example': [ + '.rp__2', + '.rp__3' + ] + }; + + expect(html(options, '

Good

', classMap)) + .toEqual('

Good

'); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + + + test('No matching classes in map', () => { + let classMap = { + '.test': [ + '.rp__background__--COLON__--OCTOTHORPF00' + ] + }; + + expect(html(options, '

Good

', classMap)) + .toEqual('

Good

'); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + }); +}); From a662d3308d2258f6066d8239dec03c98adb8a76f Mon Sep 17 00:00:00 2001 From: The Jared Wilcurt Date: Tue, 22 Dec 2020 12:37:27 -0500 Subject: [PATCH 08/21] Validate Array and Boolean --- src/validator.js | 3 ++ tests/src/html.test.js | 2 - tests/src/validator.test.js | 104 ++++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 tests/src/validator.test.js diff --git a/src/validator.js b/src/validator.js index 89afce8..5f9c29a 100644 --- a/src/validator.js +++ b/src/validator.js @@ -6,6 +6,9 @@ const validator = { key = undefined; helpers.throwError(options, message); } + if (!key) { + key = undefined; + } return key; }, validateBoolean: function (key, value) { diff --git a/tests/src/html.test.js b/tests/src/html.test.js index e5cd2e6..f33615f 100644 --- a/tests/src/html.test.js +++ b/tests/src/html.test.js @@ -39,7 +39,6 @@ describe('HTML', () => { expect(options.customLogger) .toHaveBeenCalledWith('Error parsing HTML', errorResponse); }); - }); describe('Process HTML', () => { @@ -128,7 +127,6 @@ describe('HTML', () => { .not.toHaveBeenCalled(); }); - test('No matching classes in map', () => { let classMap = { '.test': [ diff --git a/tests/src/validator.test.js b/tests/src/validator.test.js new file mode 100644 index 0000000..3840b25 --- /dev/null +++ b/tests/src/validator.test.js @@ -0,0 +1,104 @@ +const validator = require('@/validator.js'); + +describe('Validator', () => { + let options; + + beforeEach(() => { + options = { + verbose: true, + customLogger: jest.fn() + }; + }); + + describe('validateArray', () => { + test('Falsy', () => { + expect(validator.validateArray(options, false, 'message')) + .toEqual(undefined); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + + test('Non array', () => { + expect(validator.validateArray(options, 'key', 'message')) + .toEqual(undefined); + + expect(options.customLogger) + .toHaveBeenCalledWith('message', undefined); + }); + + test('Array', () => { + expect(validator.validateArray(options, ['key'], 'message')) + .toEqual(['key']); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + }); + + describe('validateBoolean', () => { + test('undefined, true', () => { + expect(validator.validateBoolean(undefined, true)) + .toEqual(true); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + + test('undefined, false', () => { + expect(validator.validateBoolean(undefined, false)) + .toEqual(false); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + + test('falsy, true', () => { + expect(validator.validateBoolean('', true)) + .toEqual(true); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + + test('truthy, false', () => { + expect(validator.validateBoolean('asdf', false)) + .toEqual(false); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + + test('true, true', () => { + expect(validator.validateBoolean(true, true)) + .toEqual(true); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + + test('false, false', () => { + expect(validator.validateBoolean(false, false)) + .toEqual(false); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + + test('true, false', () => { + expect(validator.validateBoolean(true, false)) + .toEqual(true); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + + test('false, true', () => { + expect(validator.validateBoolean(false, true)) + .toEqual(false); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + }); +}); From 83a580e8ce6c6624210ad3b77ade8301262ac890 Mon Sep 17 00:00:00 2001 From: The Jared Wilcurt Date: Tue, 22 Dec 2020 21:40:57 -0500 Subject: [PATCH 09/21] Validate File --- src/validator.js | 12 ++++-- tests/src/validator.test.js | 83 +++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 4 deletions(-) diff --git a/src/validator.js b/src/validator.js index 5f9c29a..5f6c66f 100644 --- a/src/validator.js +++ b/src/validator.js @@ -28,8 +28,10 @@ const validator = { * @return {string} Returns a string or undefined if string was invalid */ validateFile: function (options, key, extensions, checkIfExists) { - this.validateString(options, key, 'File paths must be a string'); - if (key) { + key = this.validateString(options, key, 'File paths must be a string'); + extensions = this.validateArray(options, extensions, 'extensions argument must be an array of strings containing file extensions for validation'); + + if (key && extensions && extensions.length) { let valid = extensions.some((extension) => { return key.endsWith(extension); }); @@ -40,19 +42,21 @@ const validator = { } else if (extensions.length === 2) { extensionsMessage = extensions[0] + ' or ' + extensions[1]; } else if (extensions.length > 2) { - extensionsMessage = 'one of these: ' + extensions.slice(0, -1).join(',') + ' or ' + extensions.slice(-1); + extensionsMessage = extensions.slice(0, -1).join(', ') + ', or ' + extensions.slice(-1); } helpers.throwError(options, 'Expected filepath (' + key + ') to end in ' + extensionsMessage); key = undefined; } } + if (key && checkIfExists) { let fs = require('fs'); if (!fs.existsSync(key)) { - key = undefined; helpers.throwError(options, 'Could not find file: ' + key); + key = undefined; } } + return key; }, validateFunction: function (options, key, message) { diff --git a/tests/src/validator.test.js b/tests/src/validator.test.js index 3840b25..59a789c 100644 --- a/tests/src/validator.test.js +++ b/tests/src/validator.test.js @@ -1,3 +1,4 @@ +const mockfs = require('mock-fs'); const validator = require('@/validator.js'); describe('Validator', () => { @@ -101,4 +102,86 @@ describe('Validator', () => { .not.toHaveBeenCalled(); }); }); + + describe('validateFile', () => { + describe('extensions', () => { + test('No extensions array', () => { + expect(validator.validateFile(options, 'C:\\test', undefined, false)) + .toEqual('C:\\test'); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + + test('No extension', () => { + expect(validator.validateFile(options, 'C:\\test', [], false)) + .toEqual('C:\\test'); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + + test('Does not contain extension', () => { + expect(validator.validateFile(options, 'C:\\test', ['.fail'], false)) + .toEqual(undefined); + + expect(options.customLogger) + .toHaveBeenCalledWith('Expected filepath (C:\\test) to end in .fail', undefined); + }); + + test('Does not contain either extension', () => { + expect(validator.validateFile(options, 'C:\\test', ['.fail', '.notfound'], false)) + .toEqual(undefined); + + expect(options.customLogger) + .toHaveBeenCalledWith('Expected filepath (C:\\test) to end in .fail or .notfound', undefined); + }); + + test('Does not contain any extensions', () => { + expect(validator.validateFile(options, 'C:\\test', ['.fail', '.notfound', '.bad'], false)) + .toEqual(undefined); + + expect(options.customLogger) + .toHaveBeenCalledWith('Expected filepath (C:\\test) to end in .fail, .notfound, or .bad', undefined); + }); + + test('Contains extension', () => { + expect(validator.validateFile(options, 'C:\\test.sass', ['.css', '.scss', '.sass'], false)) + .toEqual('C:\\test.sass'); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + }); + + describe('checkIfExists', () => { + afterEach(() => { + mockfs.restore(); + }); + + test('checkIfExists', () => { + mockfs({ + 'C:\\test\\file.css': '.text { background: #F00; }' + }); + + expect(validator.validateFile(options, 'C:\\test\\file.css', ['.css', '.scss', '.sass'], true)) + .toEqual('C:\\test\\file.css'); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + + test('checkIfExists', () => { + mockfs({ + 'C:\\test\\file.txt': 'text' + }); + + expect(validator.validateFile(options, 'C:\\test\\file.css', ['.css', '.scss', '.sass'], true)) + .toEqual(undefined); + + expect(options.customLogger) + .toHaveBeenCalledWith('Could not find file: C:\\test\\file.css', undefined); + }); + }); + }); }); From acb51a48c28af1ba78c3305442475d365b82aef2 Mon Sep 17 00:00:00 2001 From: The Jared Wilcurt Date: Tue, 22 Dec 2020 21:42:20 -0500 Subject: [PATCH 10/21] lint --- tests/src/validator.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/src/validator.test.js b/tests/src/validator.test.js index 59a789c..83cb15e 100644 --- a/tests/src/validator.test.js +++ b/tests/src/validator.test.js @@ -159,7 +159,7 @@ describe('Validator', () => { mockfs.restore(); }); - test('checkIfExists', () => { + test('File exists', () => { mockfs({ 'C:\\test\\file.css': '.text { background: #F00; }' }); @@ -171,7 +171,7 @@ describe('Validator', () => { .not.toHaveBeenCalled(); }); - test('checkIfExists', () => { + test('File does not exist', () => { mockfs({ 'C:\\test\\file.txt': 'text' }); From a72b3b9d1dd6439a555b89c15c69f2b7e0b1a832 Mon Sep 17 00:00:00 2001 From: The Jared Wilcurt Date: Tue, 22 Dec 2020 21:48:30 -0500 Subject: [PATCH 11/21] Validate Function --- src/validator.js | 3 +++ tests/src/validator.test.js | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/validator.js b/src/validator.js index 5f6c66f..b2f46c0 100644 --- a/src/validator.js +++ b/src/validator.js @@ -64,6 +64,9 @@ const validator = { key = undefined; helpers.throwError(options, message); } + if (!key) { + key = undefined; + } return key; }, validateObject: function (options, key, message) { diff --git a/tests/src/validator.test.js b/tests/src/validator.test.js index 83cb15e..27e6075 100644 --- a/tests/src/validator.test.js +++ b/tests/src/validator.test.js @@ -184,4 +184,31 @@ describe('Validator', () => { }); }); }); + + describe('validateFunction', () => { + test('Falsy', () => { + expect(validator.validateFunction(options, false, 'message')) + .toEqual(undefined); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + + test('Not a function', () => { + expect(validator.validateFunction(options, 'key', 'message')) + .toEqual(undefined); + + expect(options.customLogger) + .toHaveBeenCalledWith('message', undefined); + }); + + test('Function', () => { + const fn = function () {}; + expect(validator.validateFunction(options, fn, 'message')) + .toEqual(fn); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + }); }); From 34765389967256a12fc1e468951beb6e271fdc17 Mon Sep 17 00:00:00 2001 From: The Jared Wilcurt Date: Tue, 22 Dec 2020 21:50:35 -0500 Subject: [PATCH 12/21] Validate Object --- src/validator.js | 3 +++ tests/src/validator.test.js | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/validator.js b/src/validator.js index b2f46c0..14bbdc4 100644 --- a/src/validator.js +++ b/src/validator.js @@ -80,6 +80,9 @@ const validator = { key = undefined; helpers.throwError(options, message); } + if (!key) { + key = undefined; + } return key; }, validateString: function (options, key, message) { diff --git a/tests/src/validator.test.js b/tests/src/validator.test.js index 27e6075..3e44d9b 100644 --- a/tests/src/validator.test.js +++ b/tests/src/validator.test.js @@ -211,4 +211,31 @@ describe('Validator', () => { .not.toHaveBeenCalled(); }); }); + + describe('validateObject', () => { + test('Falsy', () => { + expect(validator.validateObject(options, false, 'message')) + .toEqual(undefined); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + + test('Non-object', () => { + expect(validator.validateObject(options, 'key', 'message')) + .toEqual(undefined); + + expect(options.customLogger) + .toHaveBeenCalledWith('message', undefined); + }); + + test('Object', () => { + const obj = {}; + expect(validator.validateObject(options, obj, 'message')) + .toEqual(obj); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + }); }); From 0720513728aff9c49bc3a6968489f7abf7c11ae1 Mon Sep 17 00:00:00 2001 From: The Jared Wilcurt Date: Tue, 22 Dec 2020 21:52:45 -0500 Subject: [PATCH 13/21] Validate String --- src/validator.js | 3 +++ tests/src/validator.test.js | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/validator.js b/src/validator.js index 14bbdc4..705a356 100644 --- a/src/validator.js +++ b/src/validator.js @@ -90,6 +90,9 @@ const validator = { key = undefined; helpers.throwError(options, message); } + if (!key) { + key = undefined; + } return key; }, diff --git a/tests/src/validator.test.js b/tests/src/validator.test.js index 3e44d9b..6068548 100644 --- a/tests/src/validator.test.js +++ b/tests/src/validator.test.js @@ -238,4 +238,30 @@ describe('Validator', () => { .not.toHaveBeenCalled(); }); }); + + describe('validateString', () => { + test('Falsy', () => { + expect(validator.validateString(options, false, 'message')) + .toEqual(undefined); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + + test('Non-string', () => { + expect(validator.validateString(options, {}, 'message')) + .toEqual(undefined); + + expect(options.customLogger) + .toHaveBeenCalledWith('message', undefined); + }); + + test('String', () => { + expect(validator.validateString(options, 'Test', 'message')) + .toEqual('Test'); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + }); }); From 8a66b351868acf4e781281230a643785ef2dc5a9 Mon Sep 17 00:00:00 2001 From: The Jared Wilcurt Date: Tue, 22 Dec 2020 22:36:50 -0500 Subject: [PATCH 14/21] Validate Custom Logger --- tests/src/validator.test.js | 49 +++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/tests/src/validator.test.js b/tests/src/validator.test.js index 6068548..6b7d004 100644 --- a/tests/src/validator.test.js +++ b/tests/src/validator.test.js @@ -166,7 +166,7 @@ describe('Validator', () => { expect(validator.validateFile(options, 'C:\\test\\file.css', ['.css', '.scss', '.sass'], true)) .toEqual('C:\\test\\file.css'); - + expect(options.customLogger) .not.toHaveBeenCalled(); }); @@ -178,7 +178,7 @@ describe('Validator', () => { expect(validator.validateFile(options, 'C:\\test\\file.css', ['.css', '.scss', '.sass'], true)) .toEqual(undefined); - + expect(options.customLogger) .toHaveBeenCalledWith('Could not find file: C:\\test\\file.css', undefined); }); @@ -264,4 +264,49 @@ describe('Validator', () => { .not.toHaveBeenCalled(); }); }); + + describe('validateCustomLogger', () => { + let consoleError; + + beforeEach(() => { + consoleError = console.error; + console.error = jest.fn(); + }); + + afterEach(() => { + console.error = consoleError; + consoleError = undefined; + }); + + test('Falsy', () => { + options.customLogger = false; + + expect(validator.validateCustomLogger(options).hasOwnProperty('customLogger')) + .toEqual(false); + + expect(console.error) + .not.toHaveBeenCalled(); + }); + + test('Non-function', () => { + options.customLogger = {}; + + expect(validator.validateCustomLogger(options).hasOwnProperty('customLogger')) + .toEqual(false); + + expect(console.error) + .toHaveBeenCalledWith('_________________________\nRed-Perfume:\nOptional customLogger must be a type of function.', undefined); + }); + + test('Function', () => { + expect(validator.validateCustomLogger(options).hasOwnProperty('customLogger')) + .toEqual(true); + + expect(console.error) + .not.toHaveBeenCalled(); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + }); }); From 568b310d77a1fbd94363f15ffbcc7732ccafb47c Mon Sep 17 00:00:00 2001 From: The Jared Wilcurt <4629794+TheJaredWilcurt@users.noreply.github.com> Date: Wed, 23 Dec 2020 09:44:17 -0500 Subject: [PATCH 15/21] Validate Task --- tests/src/validator.test.js | 99 +++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/tests/src/validator.test.js b/tests/src/validator.test.js index 6b7d004..39b0fd0 100644 --- a/tests/src/validator.test.js +++ b/tests/src/validator.test.js @@ -309,4 +309,103 @@ describe('Validator', () => { .not.toHaveBeenCalled(); }); }); + + describe('validateTask', () => { + test('Empty object', () => { + expect(validator.validateTask(options, {})) + .toEqual(undefined); + + expect(options.customLogger) + .toHaveBeenCalledWith('Your task does not contain styles, markup, or scripts', undefined); + }); + + test('Uglify true', () => { + expect(validator.validateTask(options, { uglify: true })) + .toEqual(undefined); + + expect(options.customLogger) + .toHaveBeenCalledWith('Your task does not contain styles, markup, or scripts', undefined); + }); + + test('Styles, data, no result', () => { + expect(validator.validateTask(options, { styles: { data: '.test { margin: 1px; }' } })) + .toEqual({ + uglify: false, + styles: { + data: '.test { margin: 1px; }' + } + }); + + expect(options.customLogger) + .toHaveBeenCalledWith('Task did not contain a task.styles.out or a task.style.result', undefined); + }); + + test('Styles, data, result', () => { + let result = jest.fn(); + + expect(validator.validateTask(options, { styles: { data: '.test { margin: 1px; }', result } })) + .toEqual({ + uglify: false, + styles: { + data: '.test { margin: 1px; }', + result + } + }); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + + test('Styles, data, out', () => { + expect(validator.validateTask(options, { styles: { data: '.test { margin: 1px; }', out: 'C:\\file.css' } })) + .toEqual({ + uglify: false, + styles: { + data: '.test { margin: 1px; }', + out: 'C:\\file.css' + } + }); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + + test('Each section is empty object', () => { + expect(validator.validateTask(options, { styles: {}, markup: [], scripts: {} })) + .toEqual({ uglify: false }); + + expect(options.customLogger) + .toHaveBeenCalledWith('Task did not contain a task.styles.in or a task.style.data', undefined); + + expect(options.customLogger) + .toHaveBeenCalledWith('Task did not contain a task.styles.out or a task.style.result', undefined); + + expect(options.customLogger) + .toHaveBeenCalledWith('Task did not contain a task.scripts.out or a task.scripts.result', undefined); + }); + + test('Each section is valid', () => { + let task = { + styles: { + data: '.test { color: #F00; }', + result: jest.fn() + }, + markup: [ + { + data: '
Hi
', + result: jest.fn() + } + ], + scripts: { + result: jest.fn() + } + }; + + expect(validator.validateTask(options, task)) + .toEqual({ uglify: false, ...task }); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + }); }); From e26d3542e3da64c94bf9b34f3d0c26bce03a5d6f Mon Sep 17 00:00:00 2001 From: The Jared Wilcurt <4629794+TheJaredWilcurt@users.noreply.github.com> Date: Wed, 23 Dec 2020 14:06:34 -0500 Subject: [PATCH 16/21] Validate task style and markup --- src/validator.js | 2 +- tests/src/validator.test.js | 90 +++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/src/validator.js b/src/validator.js index 705a356..92150e8 100644 --- a/src/validator.js +++ b/src/validator.js @@ -41,7 +41,7 @@ const validator = { extensionsMessage = extensions[0]; } else if (extensions.length === 2) { extensionsMessage = extensions[0] + ' or ' + extensions[1]; - } else if (extensions.length > 2) { + } else { extensionsMessage = extensions.slice(0, -1).join(', ') + ', or ' + extensions.slice(-1); } helpers.throwError(options, 'Expected filepath (' + key + ') to end in ' + extensionsMessage); diff --git a/tests/src/validator.test.js b/tests/src/validator.test.js index 39b0fd0..a53121a 100644 --- a/tests/src/validator.test.js +++ b/tests/src/validator.test.js @@ -370,6 +370,20 @@ describe('Validator', () => { .not.toHaveBeenCalled(); }); + test('Markup, data, result', () => { + let data = '

Hi

'; + let result = jest.fn(); + + expect(validator.validateTask(options, { markup: [{ data, result }] })) + .toEqual({ + uglify: false, + markup: [{ data, result }] + }); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + test('Each section is empty object', () => { expect(validator.validateTask(options, { styles: {}, markup: [], scripts: {} })) .toEqual({ uglify: false }); @@ -408,4 +422,80 @@ describe('Validator', () => { .not.toHaveBeenCalled(); }); }); + + describe('validateTaskStyles', () => { + test('Empty object', () => { + expect(validator.validateTaskStyles(options, {})) + .toEqual(undefined); + + expect(options.customLogger) + .toHaveBeenCalledWith('Task did not contain a task.styles.in or a task.style.data', undefined); + + expect(options.customLogger) + .toHaveBeenCalledWith('Task did not contain a task.styles.out or a task.style.result', undefined); + }); + + test('In and out', () => { + mockfs({ + 'C:\\app.css': '.app { background: #F00; }', + 'C:\\vendor.css': '.vendor { margin: 10px; }' + }); + + let styles = { + in: [ + 'C:\\app.css', + 'C:\\vendor.css' + ], + out: 'C:\\out.css' + }; + + expect(validator.validateTaskStyles(options, styles)) + .toEqual(styles); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + + mockfs.restore(); + }); + }); + + describe('validateTaskMarkup', () => { + test('Empty array', () => { + expect(validator.validateTaskMarkup(options, [])) + .toEqual(undefined); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + + test('Empty object', () => { + expect(validator.validateTaskMarkup(options, [{}])) + .toEqual(undefined); + + expect(options.customLogger) + .toHaveBeenCalledWith('Task did not contain a task.markup.in or a task.markup.data', undefined); + + expect(options.customLogger) + .toHaveBeenCalledWith('Task did not contain a task.markup.out or a task.markup.result', undefined); + }); + + test('In out', () => { + mockfs({ + 'C:\\in.html': '

Hi

' + }); + + let markup = [{ + in: 'C:\\in.html', + out: 'C:\\out.html' + }]; + + expect(validator.validateTaskMarkup(options, markup)) + .toEqual(markup); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + + mockfs.restore(); + }); + }); }); From 15be178d5f799f6f21ab93999e97c08ace34aada Mon Sep 17 00:00:00 2001 From: The Jared Wilcurt <4629794+TheJaredWilcurt@users.noreply.github.com> Date: Wed, 23 Dec 2020 14:23:46 -0500 Subject: [PATCH 17/21] Validate Task Markup Data --- tests/src/validator.test.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/src/validator.test.js b/tests/src/validator.test.js index a53121a..2013f56 100644 --- a/tests/src/validator.test.js +++ b/tests/src/validator.test.js @@ -498,4 +498,23 @@ describe('Validator', () => { mockfs.restore(); }); }); + + describe('validateTaskMarkupData', () => { + test('Invalid HTML', () => { + expect(validator.validateTaskMarkupData(options, 'Hi')) + .toEqual(undefined); + + expect(options.customLogger) + .toHaveBeenCalledWith('Optional task.markup.data must be a string that begins with \'<\' or undefined.', undefined); + }); + + test('Valid HTML', () => { + let data = '
Hi
'; + expect(validator.validateTaskMarkupData(options, data)) + .toEqual(data); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + }); }); From 9a5171994b4b40fc6a9e72e1a9a7494e69e1a195 Mon Sep 17 00:00:00 2001 From: The Jared Wilcurt <4629794+TheJaredWilcurt@users.noreply.github.com> Date: Wed, 23 Dec 2020 14:33:40 -0500 Subject: [PATCH 18/21] Validate Task Scripts --- src/validator.js | 1 + tests/src/validator.test.js | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/validator.js b/src/validator.js index 92150e8..55552c4 100644 --- a/src/validator.js +++ b/src/validator.js @@ -257,6 +257,7 @@ const validator = { return data; }, validateTaskScripts: function (options, scripts) { + scripts = scripts || {}; scripts.out = this.validateString(options, scripts.out, 'Optional task.scripts.out must be a string or undefined.'); scripts.result = this.validateFunction(options, scripts.result, 'Optional task.scripts.result must be a function or undefined.'); if (!scripts.out) { diff --git a/tests/src/validator.test.js b/tests/src/validator.test.js index 2013f56..4546c47 100644 --- a/tests/src/validator.test.js +++ b/tests/src/validator.test.js @@ -517,4 +517,36 @@ describe('Validator', () => { .not.toHaveBeenCalled(); }); }); + + describe('validateTaskScripts', () => { + test('Empty', () => { + expect(validator.validateTaskScripts(options, undefined)) + .toEqual(undefined); + + expect(options.customLogger) + .toHaveBeenCalledWith('Task did not contain a task.scripts.out or a task.scripts.result', undefined); + }); + + test('Empty object', () => { + expect(validator.validateTaskScripts(options, {})) + .toEqual(undefined); + + expect(options.customLogger) + .toHaveBeenCalledWith('Task did not contain a task.scripts.out or a task.scripts.result', undefined); + }); + + test('Out', () => { + mockfs({ + 'C:\\file.json': 'Text' + }); + + expect(validator.validateTaskScripts(options, { out: 'C:\\file.json' })) + .toEqual({ out: 'C:\\file.json' }); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + + mockfs.restore(); + }); + }); }); From edbd3f77c81d7beb1c021276430c91bb0a5613f6 Mon Sep 17 00:00:00 2001 From: The Jared Wilcurt <4629794+TheJaredWilcurt@users.noreply.github.com> Date: Wed, 23 Dec 2020 15:00:04 -0500 Subject: [PATCH 19/21] Validate Tasks --- tests/src/validator.test.js | 98 +++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/tests/src/validator.test.js b/tests/src/validator.test.js index 4546c47..f94d27a 100644 --- a/tests/src/validator.test.js +++ b/tests/src/validator.test.js @@ -310,6 +310,37 @@ describe('Validator', () => { }); }); + describe('validateTasks', () => { + test('All tasks valid', () => { + let result = jest.fn(); + options.tasks = [{ + styles: { + data: '.a{margin:1px}', + result + } + }]; + + expect(validator.validateTasks(options)) + .toEqual({ + verbose: true, + customLogger: options.customLogger, + tasks: [{ + uglify: false, + styles: { + data: '.a{margin:1px}', + result + } + }] + }); + + expect(options.customLogger) + .toHaveBeenCalledWith('Your task does not contain styles, markup, or scripts', undefined); + + expect(options.customLogger) + .toHaveBeenCalledWith('No valid tasks found.', undefined); + }); + }); + describe('validateTask', () => { test('Empty object', () => { expect(validator.validateTask(options, {})) @@ -549,4 +580,71 @@ describe('Validator', () => { mockfs.restore(); }); }); + + describe('validateOptions', () => { + test('No options', () => { + let consoleError = console.error; + console.error = jest.fn(); + let result = { + verbose: true, + tasks: [] + }; + + expect(validator.validateOptions([])) + .toEqual(result); + + expect(console.error) + .toHaveBeenCalledWith('_________________________\nRed-Perfume:\noptions.tasks Must be an array of objects. See documentation for details.', undefined); + + console.error = consoleError; + consoleError = undefined; + }); + + test('No tasks', () => { + let result = { + verbose: true, + customLogger: options.customLogger, + tasks: [] + }; + + expect(validator.validateOptions(options)) + .toEqual(result); + + expect(options.customLogger) + .toHaveBeenCalledWith('options.tasks Must be an array of objects. See documentation for details.', undefined); + }); + + test('Empty tasks array', () => { + options.tasks = []; + let result = { + verbose: true, + customLogger: options.customLogger, + tasks: [] + }; + + expect(validator.validateOptions(options)) + .toEqual(result); + + expect(options.customLogger) + .toHaveBeenCalledWith('options.tasks Must be an array of objects. See documentation for details.', undefined); + }); + + test('Tasks array empty object', () => { + options.tasks = [{}]; + let result = { + verbose: true, + customLogger: options.customLogger, + tasks: [] + }; + + expect(validator.validateOptions(options)) + .toEqual(result); + + expect(options.customLogger) + .toHaveBeenCalledWith('Your task does not contain styles, markup, or scripts', undefined); + + expect(options.customLogger) + .toHaveBeenCalledWith('No valid tasks found.', undefined); + }); + }); }); From ad9d19f907667fb7747c3d08afaed0eb1d5e84bc Mon Sep 17 00:00:00 2001 From: The Jared Wilcurt <4629794+TheJaredWilcurt@users.noreply.github.com> Date: Wed, 23 Dec 2020 15:13:13 -0500 Subject: [PATCH 20/21] cleanup --- src/html.js | 4 ++++ tests/src/validator.test.js | 5 +---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/html.js b/src/html.js index d9f04d0..8b4d714 100644 --- a/src/html.js +++ b/src/html.js @@ -18,8 +18,12 @@ function cleanDocument (document) { */ function removeParentNodes (node) { delete node.parentNode; + // Coverage ignored because this function is only used during development. + /* istanbul ignore next */ if (node.childNodes) { + /* istanbul ignore next */ node.childNodes.forEach(function (child) { + /* istanbul ignore next */ removeParentNodes(child); }); } diff --git a/tests/src/validator.test.js b/tests/src/validator.test.js index f94d27a..adc2d65 100644 --- a/tests/src/validator.test.js +++ b/tests/src/validator.test.js @@ -334,10 +334,7 @@ describe('Validator', () => { }); expect(options.customLogger) - .toHaveBeenCalledWith('Your task does not contain styles, markup, or scripts', undefined); - - expect(options.customLogger) - .toHaveBeenCalledWith('No valid tasks found.', undefined); + .not.toHaveBeenCalled(); }); }); From 114888bdf2dd541434606ee80a8fc2dd17f86139 Mon Sep 17 00:00:00 2001 From: The Jared Wilcurt <4629794+TheJaredWilcurt@users.noreply.github.com> Date: Thu, 24 Dec 2020 15:44:36 -0500 Subject: [PATCH 21/21] Index tests --- tests/index.test.js | 135 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 tests/index.test.js diff --git a/tests/index.test.js b/tests/index.test.js new file mode 100644 index 0000000..5353a29 --- /dev/null +++ b/tests/index.test.js @@ -0,0 +1,135 @@ +// const mockfs = require('mock-fs'); +// const fs = require('fs'); +const redPerfume = require('../index.js'); + +describe('Red Perfume', () => { + let options; + + beforeEach(() => { + options = { + verbose: true, + customLogger: jest.fn() + }; + }); + + describe('Atomize', () => { + test('Empty', () => { + let consoleError = console.error; + console.error = jest.fn(); + + expect(redPerfume.atomize()) + .toEqual(undefined); + + expect(console.error) + .toHaveBeenCalledWith('_________________________\nRed-Perfume:\noptions.tasks Must be an array of objects. See documentation for details.', undefined); + + console.error = consoleError; + consoleError = undefined; + }); + + /* + test('Valid options with tasks using file system', async () => { + mockfs({ + 'C:\\app.css': '.a{margin:0px;}', + 'C:\\vendor.css': '.b{padding:0px}', + 'C:\\home.html': '

Hi

', + 'C:\\about.html': '

Yo

' + }); + + options.tasks = [{ + uglify: true, + style: { + in: [ + 'C:\\app.css', + 'C:\\vendor.css' + ], + out: 'C:\\out.css', + result: function () { + try { + expect(fs.readFileSync('C:\\out.css')) + .toEqual('asdf'); + + done(); + } catch (err) { + done(err); + } + } + }, + markup: [ + { + in: 'C:\\home.html', + out: 'C:\\home.out.html' + }, + { + in: 'C:\\about.html', + out: 'C:\\about.out.html' + } + ], + scripts: { + out: 'C:\\out.json' + } + }]; + + redPerfume.atomize(options); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + + mockfs.restore(); + }); + */ + + test('Valid options with tasks using data and result', () => { + options = { + verbose: true, + customLogger: jest.fn(), + tasks: [ + { + uglify: true, + styles: { + data: '.example { padding: 10px; margin: 10px; }', + result: function (result, err) { + expect(result) + .toEqual('.rp__0 {\n padding: 10px;\n}\n.rp__1 {\n margin: 10px;\n}', undefined); + + expect(err) + .toEqual(undefined); + } + }, + markup: [ + { + data: '
', + result: function (result, err) { + expect(result) + .toEqual('
'); + + expect(err) + .toEqual(undefined); + } + } + ], + scripts: { + result: function (result, err) { + expect(result) + .toEqual({ + '.example': [ + '.rp__0', + '.rp__1' + ] + }); + + expect(err) + .toEqual(undefined); + } + } + } + ] + }; + + redPerfume.atomize(options); + + expect(options.customLogger) + .not.toHaveBeenCalled(); + }); + }); +});