Skip to content

Commit

Permalink
Add testRuleConfigs function (#68)
Browse files Browse the repository at this point in the history
  • Loading branch information
ybiquitous committed Aug 10, 2023
1 parent fa29461 commit d0ee0bc
Show file tree
Hide file tree
Showing 9 changed files with 249 additions and 7 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,9 @@
# Changelog

## Head

- Added: `testRuleConfigs` function.

## 6.1.1

- Fixed: tightly-coupled dependency on Stylelint's internal module `lib/utils/getOsEol`.
Expand Down
45 changes: 39 additions & 6 deletions README.md
Expand Up @@ -2,7 +2,7 @@

[![NPM version](https://img.shields.io/npm/v/jest-preset-stylelint.svg)](https://www.npmjs.org/package/jest-preset-stylelint) [![Build Status](https://github.com/stylelint/jest-preset-stylelint/workflows/CI/badge.svg)](https://github.com/stylelint/jest-preset-stylelint/actions)

[Jest](https://facebook.github.io/jest/) preset for [Stylelint](https://github.com/stylelint) plugins.
[Jest](https://jestjs.io/) preset for [Stylelint](https://stylelint.io/) plugins.

## Installation

Expand All @@ -22,14 +22,16 @@ Add the preset to your `jest.config.js` or `jest` field in `package.json`:
}
```

Optionally, you can avoid specifying `plugins` in every schema by defining your own setup file to configure the `testRule` function. This is useful if you have many tests. There are two additional steps to do this:
Optionally, you can avoid specifying `plugins` in every schema by defining your own setup file to configure the `testRule`/`testRuleConfigs` functions.
This is useful if you have many tests. There are two additional steps to do this:

1. Create `jest.setup.js` in the root of your project. Provide `plugins` option to `getTestRule()`:
1. Create `jest.setup.js` in the root of your project. Provide `plugins` option to `getTestRule`/`getTestRuleConfigs`:

```js
const { getTestRule } = require("jest-preset-stylelint");

global.testRule = getTestRule({ plugins: ["./"] });
global.testRuleConfigs = getTestRuleConfigs({ plugins: ["./"] });
```

2. Add `jest.setup.js` to your `jest.config.js` or `jest` field in `package.json`:
Expand All @@ -43,7 +45,13 @@ Optionally, you can avoid specifying `plugins` in every schema by defining your

## Usage

The preset exposes a global `testRule` function that you can use to efficiently test your plugin using a schema.
This preset exposes the following global functions as a helper.

See also the [type definitions](index.d.ts) for more details.

### `testRule`

The `testRule` function enables you to efficiently test your plugin using a schema.

For example, we can test a plugin that enforces and autofixes kebab-case class selectors:

Expand Down Expand Up @@ -104,9 +112,34 @@ testRule({
});
```

## Schema properties
### `testRuleConfigs`

The `testRuleConfigs` function enables you to test invalid configs for a rule.

For example:

```js
testInvalidRuleConfigs({
plugins: ["."],
ruleName,

accept: [
{
config: "valid"
}
],

See the [type definitions](index.d.ts).
reject: [
{
config: "invalid"
},
{
config: [/invalid/],
description: "regex is not allowed"
}
]
});
```

## [Changelog](CHANGELOG.md)

Expand Down
44 changes: 44 additions & 0 deletions __tests__/fixtures/plugin-foo.js
@@ -0,0 +1,44 @@
'use strict';

const stylelint = require('stylelint');

const ruleName = 'plugin/foo';

const messages = stylelint.utils.ruleMessages(ruleName, {
rejected: (selector) => `No "${selector}" selector`,
});

/** @type {(value: unknown) => boolean} */
const isString = (value) => typeof value === 'string';

/** @type {import('stylelint').Rule} */
const ruleFunction = (primary) => {
return (root, result) => {
const validOptions = stylelint.utils.validateOptions(result, ruleName, {
actual: primary,
possible: [isString],
});

if (!validOptions) {
return;
}

root.walkRules((rule) => {
const { selector } = rule;

if (primary !== selector) {
stylelint.utils.report({
result,
ruleName,
message: messages.rejected(selector),
node: rule,
});
}
});
};
};

ruleFunction.ruleName = ruleName;
ruleFunction.messages = messages;

module.exports = stylelint.createPlugin(ruleName, ruleFunction);
33 changes: 33 additions & 0 deletions __tests__/getTestRule.test.js
@@ -0,0 +1,33 @@
'use strict';

const getTestRule = require('../getTestRule.js');

const testRule = getTestRule();

testRule({
plugins: [require.resolve('./fixtures/plugin-foo.js')],
ruleName: 'plugin/foo',
config: ['.a'],

accept: [
{
code: '.a {}',
},
{
code: '.a {}',
description: 'with description',
},
],

reject: [
{
code: '#a {}',
message: 'No "#a" selector (plugin/foo)',
},
{
code: '#a {}',
message: 'No "#a" selector (plugin/foo)',
description: 'with description',
},
],
});
30 changes: 30 additions & 0 deletions __tests__/getTestRuleConfigs.test.js
@@ -0,0 +1,30 @@
'use strict';

const getTestRuleConfigs = require('../getTestRuleConfigs.js');

const testRuleConfigs = getTestRuleConfigs();

testRuleConfigs({
plugins: [require.resolve('./fixtures/plugin-foo.js')],
ruleName: 'plugin/foo',

accept: [
{
config: 'a',
},
{
config: ['b'],
description: 'string is allowed',
},
],

reject: [
{
config: 123,
},
{
config: [/foo/],
description: 'regex is not allowed',
},
],
});
68 changes: 68 additions & 0 deletions getTestRuleConfigs.js
@@ -0,0 +1,68 @@
'use strict';

const { inspect } = require('util');

/** @type {import('.').getTestRuleConfigs} */
module.exports = function getTestRuleConfigs(options = {}) {
return function testRuleConfigs({
ruleName,
accept = [],
reject = [],
only = false,
skip = false,
plugins = options.plugins,
}) {
if (accept.length === 0 && reject.length === 0) {
throw new TypeError('The either "accept" or "reject" property must not be empty');
}

/** @type {import('stylelint').lint} */
let lint;

beforeAll(() => {
// eslint-disable-next-line n/no-unpublished-require
lint = require('stylelint').lint;
});

const testGroup = only ? describe.only : skip ? describe.skip : describe;

testGroup(`${ruleName} configs`, () => {
/**
* @param {import('.').ConfigCase} case
* @param {(warnings: unknown[]) => void} comparison
*/
function testConfig({ config, description, only: onlyTest, skip: skipTest }, comparison) {
const testFn = onlyTest ? test.only : skipTest ? test.skip : test;

testFn(`${description || inspect(config)}`, async () => {
const lintConfig = {
plugins,
rules: { [ruleName]: config },
};
const { results } = await lint({ code: '', config: lintConfig });

expect(results).toHaveLength(1);
comparison(results[0].invalidOptionWarnings);
});
}

describe('accept', () => {
accept.forEach((c) => {
testConfig(c, (warnings) => {
// eslint-disable-next-line jest/no-standalone-expect
expect(warnings).toEqual([]);
});
});
});

describe('reject', () => {
reject.forEach((c) => {
testConfig(c, (warnings) => {
// eslint-disable-next-line jest/no-standalone-expect
expect(warnings).toEqual([{ text: expect.stringMatching(`"${ruleName}"`) }]);
});
});
});
});
};
};
23 changes: 23 additions & 0 deletions index.d.ts
Expand Up @@ -156,6 +156,29 @@ export type TestRule = (schema: TestSchema) => void;
*/
export function getTestRule(options?: { plugins?: TestSchema['plugins'] }): TestRule;

export type ConfigCase = {
config: unknown;
description?: string;
only?: boolean;
skip?: boolean;
};

/**
* Test configurations for a rule.
*/
export type TestRuleConfigs = (
schema: Pick<TestSchema, 'ruleName' | 'plugins' | 'only' | 'skip'> & {
accept?: ConfigCase[];
reject?: ConfigCase[];
},
) => void;

/**
* Create a `testRuleConfigs()` function with any specified plugins.
*/
export function getTestRuleConfigs(options?: { plugins?: TestSchema['plugins'] }): TestRuleConfigs;

declare global {
var testRule: TestRule;
var testRuleConfigs: TestRuleConfigs;
}
4 changes: 3 additions & 1 deletion jest-setup.js
@@ -1,5 +1,7 @@
'use strict';

const getTestRule = require('./getTestRule');
const getTestRule = require('./getTestRule.js');
const getTestRuleConfigs = require('./getTestRuleConfigs.js');

global.testRule = getTestRule();
global.testRuleConfigs = getTestRuleConfigs();
5 changes: 5 additions & 0 deletions package.json
Expand Up @@ -14,6 +14,7 @@
"main": "index.js",
"types": "index.d.ts",
"files": [
"getTestRuleConfigs.js",
"getTestRule.js",
"jest-preset.js",
"jest-setup.js",
Expand Down Expand Up @@ -55,6 +56,10 @@
"@stylelint/remark-preset"
]
},
"jest": {
"preset": "./jest-preset.js",
"testRegex": ".*\\.test\\.js$"
},
"devDependencies": {
"@stylelint/prettier-config": "^3.0.0",
"@stylelint/remark-preset": "^4.0.0",
Expand Down

0 comments on commit d0ee0bc

Please sign in to comment.