From 4ba2368977f5589573aeb6ca42b2935ca8b2704b Mon Sep 17 00:00:00 2001 From: Shawn Allen Date: Thu, 3 May 2018 15:15:37 -0700 Subject: [PATCH] refactor docs test: more async, one test per class --- tests/modules/test-document-styles.js | 116 +++++++++++++++----------- 1 file changed, 69 insertions(+), 47 deletions(-) diff --git a/tests/modules/test-document-styles.js b/tests/modules/test-document-styles.js index 89dc4367..530e1b73 100644 --- a/tests/modules/test-document-styles.js +++ b/tests/modules/test-document-styles.js @@ -1,73 +1,95 @@ -const test = require("ava") -const css = require(process.cwd()) -const fs = require("fs") -const glob = require("glob") +const {join} = require('path') +const fse = require('fs-extra') +const globby = require('globby') +const test = require('ava') +const minimatch = require('minimatch') -let selectors -let classnames +const cwd = process.cwd() +const css = require(cwd) +const pkg = require(join(cwd, 'package.json')) +const unique = list => Array.from(new Set(list)).sort() + +/* + * These are the regular expressions that match what we + * expect to be class name instances in the docs. + * Patterns should group the matched class name(s) such that: + * + * ```js + * const [, klass, ] = pattern.exec(content) + * ``` + */ const classPatterns = [ // HTML class attributes /class="([^"]+)"/ig, + /:class ?=> "([^"]+)"/g, + /class: "([^"]+)"/g, // assume that ERB helpers generate an element with the same class /<%= (\w+)\b/g, ] +const whitelistClasses = (pkg.primer ? pkg.primer.class_whitelist || [] : []) + .concat('js*') + +const isWhitelisted = klass => { + return whitelistClasses.some(glob => minimatch(klass, glob)) +} + // Find unique selectors from the cssstats selector list -function uniqueSelectors(s) { - s = s.map(s => { +function uniqueSelectors(selectors) { + return unique(selectors.map(s => { // split multi-selectors into last class used .foo .bar .baz - return s.split(" ").pop() + return s.split(' ').pop() }) .filter(s => { - // remove any selector that aren't just regular classnames eg. ::hover [type] + // only match exact class selectors return s.match(/^\.[a-z\-_]+$/ig) - }) - - // return only the unique selectors - return [...new Set(s)] + })) } // From the given glob sources array, read the files and return found classnames -function documentedClassnames(sources) { - const classes = [] - const files = sources.reduce((acc, pattern) => { - return acc.concat(glob.sync(pattern)) - }, []) - - files.forEach(file => { - let match = null - const content = fs.readFileSync(file, "utf8") - - classPatterns.forEach(pattern => { - // match each pattern against the source - while (match = pattern.exec(content)) { - // get the matched classnames and split by whitespace into classes - const klasses = match[1].trim().split(/\s+/) - classes.push(...klasses) - } +function getDocumentedClassnames(sources) { + return globby(sources) + .then(paths => { + return Promise.all(paths.map(path => { + return fse.readFile(path, 'utf8') + .then(content => ({path, content})) + })) }) - }) - - // return only the unique classnames - return Array.from(new Set(classes)) + .then(files => { + return files.reduce((classes, {path, content}) => { + classPatterns.forEach(pattern => { + let match + while (match = pattern.exec(content)) { + // get the matched classnames and split by whitespace into classes + let klasses = match[1].trim().split(/\s+/) + classes.push(...klasses) + } + }) + return classes + }, []) + }) + .then(classes => unique(classes)) } -// Before all the tests get the selectors and classnames +const selectors = uniqueSelectors(css.cssstats.selectors.values) +let classnames test.before(t => { - selectors = uniqueSelectors(css.cssstats.selectors.values) - classnames = documentedClassnames([ - "docs/*.md", - "README.md" - ]) + return getDocumentedClassnames([ + 'docs/*.md', + 'README.md' + ]) + .then(_ => (classnames = _)) }) -test("Every selector class is documented", t => { - const undocumented = [] - selectors.forEach(selector => { - if (!classnames.includes(selector.replace(/^\./, ""))) { - undocumented.push(selector) +selectors.forEach(selector => { + const klass = selector.replace(/^\./, '') + test(`Selector "${selector}" is documented/whitelisted`, t => { + t.plan(1) + if (isWhitelisted(klass)) { + t.pass(`Selector "${selector}" is whitelisted`) + } else { + t.is(classnames.includes(klass), true, `Selector "${selector}" is not documented`) } }) - t.is(undocumented.length, 0, `I did not find documentation for the "${undocumented.join(", ")}" selector(s) in the ${process.env.npm_package_name} module.`); })