testRule
The function returned by the createTestRule
factory, this is the main function for testing user scenarios.
The function must always contain config
and at least one accept
or reject
test.
Minimal use case
Note
It is assumed that you have declared the createTestRule
function globally as specified in the Guide
section of the documentation and the plugins
key is present within createTestUtils
.
import { yourRule } from './your-rule.ts';
const { ruleName } = yourRule;
const testRule = createTestRule({ ruleName });
testRule({
config: true,
accept: [
{ code: '.foo {}' },
],
});
// or
testRule({
config: [true, { option: false }],
reject: [
{ code: '.bar {}' },
],
});
Options
You can always see the actual options in the source code here.
ruleName
The same option as described in createTestRule > Options > ruleName
but takes precedence over it if specified, allowing to overwrite the defaults for a particular rule.
Note
It is recommended to define ruleName
within createTestRule
function to reduce the boilerplate code. The option only exists to ensure that the project can be a drop-in replacement for an existing solutions for testing Stylelint plugins.
Show original description
The name of the rule that is being tested.
Used for output in the console, and for binding the config
property of the testRule
function to a rule from the plugins
list.
plugins
The same option as described in createTestUtils > Options > plugins
but takes precedence over it if specified, allowing to overwrite the defaults for a particular rule.
Note
It is recommended to define plugins
within createTestUtils
or createTestRule
functions to reduce the boilerplate code. The option only exists to ensure that the project can be a drop-in replacement for an existing solutions for testing Stylelint plugins.
Show original description
The name of the rule that is being tested.
Used for output in the console, and for binding the config
property of the testRule
function to a rule from the plugins
list.
extraRules
The same option as described in createTestUtils > Options > extraRules
but, if specified, appended to these rules.
Show original description
Object in form compatible with Stylelint's rules
property, that allows to run stylelint
with extra rules in addition to the one being tested.
description
Description of the test group.
It is displayed in the console and makes it easier to identify the test when necessary.
testRule({
description: '"ignore" option as array of strings',
config: [true, { ignore: ['foo', 'bar'] }],
accept: [
{ code: '.foo {}' }
{ code: '.bar {}' }
]
})
✓ {rule-name}: "ignore" option as array of strings (2)
✓ accept (2)
✓ Accept test case №1
✓ Accept test case №2
config
The configuration passed to the rule with the name as passed in ruleName
to the createTestRule()
function.
import { yourRule } from './your-rule.ts';
// Assume your rule is named `@scope/your-rule`
const { ruleName } = yourRule;
const testRule = createTestRule({ ruleName });
testRule({
config: [true, { ignore: ['foo'] }],
accept: [
{ code: '.foo {}' },
],
});
stylelint.lint({
code: '.foo {}',
config: {
rules: {
'@scope/your-rule': [true, { ignore: ['foo'] }],
},
},
});
codeFilename
Maps to Stylelint's codeFilename
option.
Useful if your rule is supposed to work differently depending on the file name/path, for example:
- Has different logic depending on the file extension (
.css
or.scss
for example); - Should only work on files whose path matches a certain pattern.
import { yourRule } from './your-rule.ts';
const { ruleName } = yourRule;
const testRule = createTestRule({ ruleName });
testRule({
config: [true],
codeFilename: 'the-component.scss',
accept: [
{ code: '.the-component {}' },
],
});
export const yourRule = (primary, secondaryOptions, context) => {
return (root, result) => {
console.log(root.source?.input.file); // the-component.scss
};
};
customSyntax
The same option as described in createTestUtils > Options > customSyntax
and createTestRule > Options > customSyntax
but takes precedence over them if specified, allowing to overwrite the defaults for a particular set.
Show original description
Maps to Stylelint's customSyntax
configuration property, has the same signature.
accept
& reject
An array of tests that should pass without warnings from Stylelint or where an error is expected, respectively.
See more info about test cases in Test cases section below.
skip
& only
Controls whether to skip the group of tests or run only that group.
// Only tests of this group will be run because of the `only` flag
testRule({
config: true,
only: true,
accept: [
{ code: '.foo {}' },
{ code: '.bar {}' },
],
});
testRule({
config: [true, { ignore: 'baz' }],
accept: [
{ code: '.baz {}' },
{ code: '.qwe {}' },
],
});
autoStripIndent
The same option as described in createTestUtils > Options > autoStripIndent
but takes precedence over it if specified, allowing to overwrite the defaults for a particular rule.
Show original description
Controls whether indentation should be automatically stripped out of code blocks.
/**
* @default false
*/
type AutoStripIndent = boolean;
Um, why?
It can be quite tedious to calculate error positions when testing complex multi-line rules.
Let's pretend we have the following sample code and we expect to see an error highlighting on the .another-component
selector:
{
description: 'Side-effect within `@media`-query on the root level',
code: `
.the-component {}
@media (max-width: 320px) {
.another-component {}
}
`,
}
You would say that the error should start on line 4
and column 3
, wouldn't you?
But in fact, for this particular code block, the error will start on line 5
and column 9
!
This is because the input is a string that contains all linebreaks and indentation:
`
.the-component {}
@media (max-width: 320px) {
.another-component {}
}
`
Not very similar to how our CSS usually looks like.
Imagine you are designing a rule that interacts with indentation...
With the autoStripIndent
option enabled, all code blocks automatically remove the start/end spaces as well as the extra indentation, so the string becomes exactly what we would see in the CSS file:
.the-component {}
@media (max-width: 320px) {
.another-component {}
}
Test cases
There are two types of tests:
accept
(code which Stylelint should not complain about);reject
(code where a Stylelint error is expected).
They are located in the accept
and reject
keys of the testRule()
options respectively.
Each group of tests described by the testRule()
function must contain at least one test - no matter whether it is accepted or rejected.
accept
test case
These tests contain user scenarios that should pass Stylelint validation without warnings.
Its properties described below:
code
Each test must necessarily have a code
property, which is the string that is validated by Stylelint. A test without code
just doesn't make sense.
You can automatically remove indentation in the case of multi-line input using the autoStripIndent
option.
testRule({
config: true,
accept: [
{ code: '.foo {}' }
{
code: `
.foo {
@media (max-width: 480px) {
color: red;
}
}
`
}
]
})
description
A test can have a description - it is displayed in the console and makes it easier to find the test if necessary.
If no description is provided, then instead of description will be displayed either the test sequence number (by default) or its code, depending on the testCaseWithoutDescriptionAppearance
option.
testRule({
config: true,
accept: [
{
description: 'Works with the simple selector',
code: '.foo {}',
},
],
});
✓ {rule-name}: group №1 (1)
✓ accept (1)
✓ Works with the simple selector
codeFilename
The same option as described in codeFilename
above but takes precedence over it if specified, allowing to overwrite the defaults for a particular rule.
Show original description
Maps to Stylelint's codeFilename
option.
Useful if your rule is supposed to work differently depending on the file name/path, for example:
- Has different logic depending on the file extension (
.css
or.scss
for example); - Should only work on files whose path matches a certain pattern.
customSyntax
The same option as described in customSyntax
above but takes precedence over it if specified, allowing to overwrite the defaults for a particular rule.
Show original description
Maps to Stylelint's customSyntax
configuration property, has the same signature.
autoStripIndent
The same option as described in autoStripIndent
above but takes precedence over it if specified, allowing to overwrite the defaults for a particular rule.
Show original description
Controls whether indentation should be automatically stripped out of code blocks.
/**
* @default false
*/
type AutoStripIndent = boolean;
Um, why?
It can be quite tedious to calculate error positions when testing complex multi-line rules.
Let's pretend we have the following sample code and we expect to see an error highlighting on the .another-component
selector:
{
description: 'Side-effect within `@media`-query on the root level',
code: `
.the-component {}
@media (max-width: 320px) {
.another-component {}
}
`,
}
You would say that the error should start on line 4
and column 3
, wouldn't you?
But in fact, for this particular code block, the error will start on line 5
and column 9
!
This is because the input is a string that contains all linebreaks and indentation:
`
.the-component {}
@media (max-width: 320px) {
.another-component {}
}
`
Not very similar to how our CSS usually looks like.
Imagine you are designing a rule that interacts with indentation...
With the autoStripIndent
option enabled, all code blocks automatically remove the start/end spaces as well as the extra indentation, so the string becomes exactly what we would see in the CSS file:
.the-component {}
@media (max-width: 320px) {
.another-component {}
}
skip
& only
flags
Controls whether to skip the test or run the only test.
testRule({
config: true,
accept: [
{ code: '.foo {}' },
// Only this test will be run because of `only` flag.
{
only: true,
code: '.bar {}',
},
],
});
reject
test case
These tests contain user input where a Stylelint warning(s) is expected.
Can contain all the same properties as the accept
test, but in addition:
message
All reject
tests must have at least one additional field - message
.
It can be declared either in the case itself (if a single error is expected) or inside its warnings
if more than one error is expected or if you prefer to always use warnings
, even for a single test case.
import { yourRule } from './your-rule.ts';
const { ruleName, messages } = yourRule;
const testRule = createTestRule({ ruleName });
testRule({
config: true,
reject: [
{
code: '.foo {}',
message: messages.unexpected('.foo'),
},
],
});
import { yourRule } from './your-rule.ts';
const { ruleName, messages } = yourRule;
const testRule = createTestRule({ ruleName });
testRule({
config: true,
reject: [
{
code: '.foo {}',
warnings: [
{ message: messages.unexpected('.foo') },
],
},
],
});
fixed
If the rule contains a fixer, you can test its operation using the fixed
property.
// Let's pretend we are testing a rule that
// disallows the use of UPPERCASE and has a fixer
testRule({
description: '"ignore" option as array of strings',
config: true,
reject: [
{
code: '.THE-SELECTOR {}',
fixed: '.the-selector {}',
}
]
})
Additional features
The function can be called using its own skip
and only
methods to make these modifiers more visible when using IDEs folding feature.
testRule.skip({
config: [true],
accept: [
{ code: '.foo {}' },
],
});
// The same as:
testRule({
skip: true,
config: [true],
accept: [
{ code: '.foo {}' },
],
});