1
1
mirror of https://github.com/primer/css.git synced 2024-11-26 23:56:04 +03:00

refactor docs test: more async, one test per class

This commit is contained in:
Shawn Allen 2018-05-03 15:15:37 -07:00
parent ab9cc3e0ea
commit 4ba2368977

View File

@ -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.`);
})