mirror of
https://github.com/primer/css.git
synced 2024-12-27 16:11:46 +03:00
refactor docs test: more async, one test per class
This commit is contained in:
parent
ab9cc3e0ea
commit
4ba2368977
@ -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.`);
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user