Skip to content

Commit 9b9c8eb

Browse files
ybiquitousjeddy3
andauthored
Port code from jest-preset-stylelint (#4)
See https://github.com/stylelint/jest-preset-stylelint Co-authored-by: Richard Hallows <jeddy3@users.noreply.github.com>
1 parent f0ce26a commit 9b9c8eb

11 files changed

+804
-13
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Changelog
2+
3+
## 0.1.0
4+
5+
Initial release.

README.md

+160
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,163 @@
33
[![CI](https://github.com/stylelint/stylelint-test-rule-node/actions/workflows/ci.yml/badge.svg)](https://github.com/stylelint/stylelint-test-rule-node/actions/workflows/ci.yml)
44

55
A Stylelint rule tester using [Node.js built-in test runner](https://nodejs.org/api/test.html) (`node:test`).
6+
7+
## Installation
8+
9+
Install the tester package alongside Stylelint:
10+
11+
```shell
12+
npm install stylelint-test-rule-node stylelint --save-dev
13+
```
14+
15+
## Usage
16+
17+
First, write a test file for rules you want to test. For example:
18+
19+
```js
20+
// block-no-empty.test.js
21+
import { testRule } from "stylelint-test-rule-node";
22+
23+
testRule({
24+
ruleName: "block-no-empty",
25+
config: true,
26+
27+
accept: [
28+
{
29+
code: "a { color: red }"
30+
}
31+
],
32+
33+
reject: [
34+
{
35+
code: "a {}",
36+
message: "Unexpected empty block (block-no-empty)"
37+
}
38+
]
39+
});
40+
```
41+
42+
Then, run the test via `node --test`:
43+
44+
```sh-session
45+
$ node --test block-no-empty.test.js
46+
...
47+
▶ block-no-empty (28.773291ms)
48+
49+
ℹ tests 2
50+
ℹ suites 7
51+
ℹ pass 2
52+
ℹ fail 0
53+
ℹ cancelled 0
54+
ℹ skipped 0
55+
ℹ todo 0
56+
ℹ duration_ms 158.194084
57+
```
58+
59+
See also the [type definitions](lib/index.d.ts) for more details.
60+
61+
### `testRule`
62+
63+
The `testRule` function enables you to efficiently test your plugin using a schema.
64+
65+
For example, we can test a plugin that enforces and autofixes kebab-case class selectors:
66+
67+
```js
68+
// my-plugin.test.js
69+
import { testRule } from "stylelint-test-rule-node";
70+
71+
import myPlugin from "./my-plugin.js";
72+
73+
const plugins = [myPlugin];
74+
const {
75+
ruleName,
76+
rule: { messages }
77+
} = myPlugin;
78+
79+
testRule({
80+
plugins,
81+
ruleName,
82+
config: [true, { type: "kebab" }],
83+
fix: true,
84+
85+
accept: [
86+
{
87+
code: ".class {}",
88+
description: "simple class selector"
89+
},
90+
{
91+
code: ".my-class {}",
92+
description: "kebab class selector"
93+
}
94+
],
95+
96+
reject: [
97+
{
98+
code: ".myClass {}",
99+
fixed: ".my-class {}",
100+
description: "camel case class selector",
101+
message: messages.expected(),
102+
line: 1,
103+
column: 1,
104+
endLine: 1,
105+
endColumn: 8
106+
},
107+
{
108+
code: ".MyClass,\n.MyOtherClass {}",
109+
fixed: ".my-class,\n.my-other-class {}",
110+
description: "two pascal class selectors in a selector list",
111+
warnings: [
112+
{
113+
message: messages.expected(),
114+
line: 1,
115+
column: 1,
116+
endLine: 1,
117+
endColumn: 8
118+
},
119+
{
120+
message: messages.expected(),
121+
line: 2,
122+
column: 1,
123+
endLine: 2,
124+
endColumn: 13
125+
}
126+
]
127+
}
128+
]
129+
});
130+
```
131+
132+
### `testRuleConfigs`
133+
134+
The `testRuleConfigs` function enables you to test invalid configs for a rule.
135+
136+
For example:
137+
138+
```js
139+
import { testRuleConfigs } from "stylelint-test-rule-node";
140+
141+
testRuleConfigs({
142+
plugins,
143+
ruleName,
144+
145+
accept: [
146+
{
147+
config: "valid"
148+
}
149+
],
150+
151+
reject: [
152+
{
153+
config: "invalid"
154+
},
155+
{
156+
config: [/invalid/],
157+
description: "regex is not allowed"
158+
}
159+
]
160+
});
161+
```
162+
163+
## [Changelog](CHANGELOG.md)
164+
165+
## [License](LICENSE)

lib/__tests__/fixtures/plugin-foo.js

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import stylelint from 'stylelint';
2+
3+
const {
4+
// @ts-expect-error -- TS2339: Property 'createPlugin' does not exist on type 'typeof import("...").'
5+
createPlugin,
6+
// @ts-expect-error -- TS2339: Property 'utils' does not exist on type 'typeof import("...")'.
7+
utils: { report, ruleMessages, validateOptions },
8+
} = stylelint;
9+
10+
const ruleName = 'plugin/foo';
11+
12+
const messages = ruleMessages(ruleName, {
13+
rejected: (/** @type {string} */ selector) => `No "${selector}" selector`,
14+
});
15+
16+
/** @type {(value: unknown) => boolean} */
17+
const isString = (value) => typeof value === 'string';
18+
19+
/** @type {import('stylelint').Rule} */
20+
const ruleFunction = (primary, _secondaryOptions, { fix }) => {
21+
return (root, result) => {
22+
const validOptions = validateOptions(result, ruleName, {
23+
actual: primary,
24+
possible: [isString],
25+
});
26+
27+
if (!validOptions) {
28+
return;
29+
}
30+
31+
root.walkRules((rule) => {
32+
const { selector } = rule;
33+
34+
if (primary === selector) return;
35+
36+
if (fix) {
37+
rule.selector = primary;
38+
39+
return;
40+
}
41+
42+
report({
43+
result,
44+
ruleName,
45+
message: messages.rejected(selector),
46+
node: rule,
47+
word: selector,
48+
});
49+
});
50+
};
51+
};
52+
53+
ruleFunction.ruleName = ruleName;
54+
ruleFunction.messages = messages;
55+
56+
export default createPlugin(ruleName, ruleFunction);

lib/__tests__/testRule.test.js

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { testRule } from '../index.js';
2+
3+
import plugin from './fixtures/plugin-foo.js';
4+
5+
const plugins = [plugin];
6+
const {
7+
ruleName,
8+
rule: { messages },
9+
} = plugin;
10+
11+
testRule({
12+
plugins,
13+
ruleName,
14+
config: ['.a'],
15+
16+
accept: [
17+
{
18+
code: '.a {}',
19+
},
20+
{
21+
code: '.a {}',
22+
description: 'with description',
23+
},
24+
],
25+
26+
reject: [
27+
{
28+
code: '#a {}',
29+
message: messages.rejected('#a'),
30+
},
31+
{
32+
code: '#a {}',
33+
message: messages.rejected('#a'),
34+
description: 'with description',
35+
},
36+
{
37+
code: '#a {}',
38+
message: messages.rejected('#a'),
39+
description: 'with location',
40+
line: 1,
41+
column: 1,
42+
endLine: 1,
43+
endColumn: 3,
44+
},
45+
{
46+
code: '#a {} #b {}',
47+
description: 'multiple warnings',
48+
warnings: [
49+
{
50+
message: messages.rejected('#a'),
51+
},
52+
{
53+
message: messages.rejected('#b'),
54+
line: 1,
55+
column: 7,
56+
endLine: 1,
57+
endColumn: 9,
58+
},
59+
],
60+
},
61+
{
62+
code: '#a {',
63+
message: 'Unclosed block (CssSyntaxError)',
64+
description: 'syntax error',
65+
},
66+
],
67+
});
68+
69+
testRule({
70+
plugins,
71+
ruleName,
72+
config: ['.a'],
73+
fix: true,
74+
75+
reject: [
76+
{
77+
code: '#a {}',
78+
fixed: '.a {}',
79+
message: messages.rejected('#a'),
80+
},
81+
],
82+
});

lib/__tests__/testRuleConfigs.test.js

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { testRuleConfigs } from '../index.js';
2+
3+
import plugin from './fixtures/plugin-foo.js';
4+
5+
const plugins = [plugin];
6+
const { ruleName } = plugin;
7+
8+
testRuleConfigs({
9+
plugins,
10+
ruleName,
11+
12+
accept: [
13+
{
14+
config: 'a',
15+
},
16+
{
17+
config: ['b'],
18+
description: 'string is allowed',
19+
},
20+
],
21+
22+
reject: [
23+
{
24+
config: 123,
25+
},
26+
{
27+
config: [/foo/],
28+
description: 'regex is not allowed',
29+
},
30+
],
31+
});

lib/__tests__/types.test.ts

-5
This file was deleted.

0 commit comments

Comments
 (0)