@jhae/stylelint-config-verifier
Version:
A Stylelint configuration tester for Jest that helps you verify your defined rules.
140 lines (139 loc) • 4.34 kB
JavaScript
import stylelint from 'stylelint';
/**
* The `ConfigVerifier` class verifies the rules of a Stylelint configuration. It runs Stylelint for a given test case
* and compares the linter result with the expected result. The test case contains the code that should be linted and
* the expected result. The expected result contains the expected error status, messages, and severities.
*
* @example
* ```typescript
* new ConfigVerifier().verify(
* 'at-rule-disallowed-list',
* {
* name: 'Disallow @debug rule',
* code: '@debug "";',
* expect: {
* errored: true,
* messages: ['Unexpected at-rule "debug"'],
* severities: ['error'],
* },
* },
* {
* name: 'Allow @use rule',
* code: '@use "test.scss";',
* },
* );
* ``
*/
export class ConfigVerifier {
configFile;
/**
* The default test case expectation
*
* This expectation occurs if Stylelint reports no problems and is used if no expectation was defined in a test case.
*
* @type {TestCaseExpectation}
*
* @internal
*/
defaultExpectation = {
errored: false,
messages: [],
severities: [],
};
/**
* Creates a new `ConfigVerifier` object.
*
* @param {string} configFile The path to the Stylelint config file whose rules should be verified
*/
constructor(configFile = '.stylelintrc.yaml') {
this.configFile = configFile;
}
/**
* Verifies a rule configuration.
*
* @param {string} ruleName The name of the rule
* @param {TestCase[]} testCases The test cases
*/
verify(ruleName, ...testCases) {
describe(`Rule '${ruleName}'`, () => {
test.each(testCases)('$name', async (testCase) => {
const warnings = this.getWarnings(ruleName, await this.getLinterResult(testCase));
const { expect: expectation = this.defaultExpectation } = testCase;
expect(this.getErrored(warnings)).toBe(expectation.errored);
expect(this.getMessages(warnings)).toStrictEqual(expectation.messages.map((message) => `${message} (${ruleName})`));
expect(this.getSeverities(warnings)).toStrictEqual(expectation.severities);
});
});
}
/**
* Runs Stylelint for the given test case and returns a Promise that resolves to the linter result.
*
* @internal
*
* @param {TestCase} testCase The test case
*
* @return {Promise<LinterResult>}
*/
getLinterResult({ file, code }) {
if ([file, code].filter((value) => value === undefined).length !== 1) {
throw new Error('Though both "file" and "code" are optional, you must have one and cannot have both.');
}
return stylelint.lint({
configFile: this.configFile,
files: file,
code,
});
}
/**
* Returns the warnings for a rule from the given linter result.
*
* @internal
*
* @param {string} ruleName The name of the rule
* @param {LinterResult} linterResult The linter result
*
* @return {Warning[]}
*/
getWarnings(ruleName, { results: lintResults }) {
return lintResults
.map(({ warnings }) => warnings)
.reduce((previous, current) => previous.concat(current), [])
.filter((warning) => warning.rule === ruleName);
}
/**
* Returns true if the given lint warnings contain an error, otherwise false.
*
* @internal
*
* @param {Warning[]} warnings The lint warnings
*
* @return {boolean}
*/
getErrored(warnings) {
return this.getSeverities(warnings).some((severity) => severity === 'error');
}
/**
* Returns the messages of the given lint warnings.
*
* @internal
*
* @param {Warning[]} warnings The lint warnings
*
* @return {string[]}
*/
getMessages(warnings) {
return warnings.map(({ text }) => text);
}
/**
* Returns the severities of the given lint warnings.
*
* @internal
*
* @param {Warning[]} warnings The lint warnings
*
* @return {Severity[]}
*/
getSeverities(warnings) {
return warnings.map(({ severity }) => severity);
}
}