Skip to content

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.

ts
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.

ts
testRule({
  description: '"ignore" option as array of strings',
  config: [true, { ignore: ['foo', 'bar'] }],
  accept: [
    { code: '.foo {}' }
    { code: '.bar {}' }
  ]
})
bash
 {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.

ts
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 {}' },
  ],
});
ts
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:

  1. Has different logic depending on the file extension (.css or .scss for example);
  2. Should only work on files whose path matches a certain pattern.
ts
import { yourRule } from './your-rule.ts';

const { ruleName } = yourRule;
const testRule = createTestRule({ ruleName });

testRule({
  config: [true],
  codeFilename: 'the-component.scss',
  accept: [
    { code: '.the-component {}' },
  ],
});
ts
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.

ts
// 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.

ts
/**
 * @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:

ts
{
  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:

text
`
      .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:

text
.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.

ts
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.

ts
testRule({
  config: true,
  accept: [
    {
      description: 'Works with the simple selector',
      code: '.foo {}',
    },
  ],
});
bash
 {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:

  1. Has different logic depending on the file extension (.css or .scss for example);
  2. 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.

ts
/**
 * @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:

ts
{
  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:

text
`
      .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:

text
.the-component {}

@media (max-width: 320px) {
  .another-component {}
}

skip & only flags

Controls whether to skip the test or run the only test.

ts
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.

ts
import { yourRule } from './your-rule.ts';

const { ruleName, messages } = yourRule;
const testRule = createTestRule({ ruleName });

testRule({
  config: true,
  reject: [
    {
      code: '.foo {}',
      message: messages.unexpected('.foo'),
    },
  ],
});
ts
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.

ts
// 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.

ts
testRule.skip({ 
  config: [true],
  accept: [
    { code: '.foo {}' },
  ],
});

// The same as:

testRule({ 
  skip: true, 
  config: [true],
  accept: [
    { code: '.foo {}' },
  ],
});