Skip to content

Commit

Permalink
feat(valid-title): support mustMatch option
Browse files Browse the repository at this point in the history
Closes #233
  • Loading branch information
G-Rath committed Jun 21, 2020
1 parent 8e6990e commit a70a4cc
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 8 deletions.
39 changes: 37 additions & 2 deletions docs/rules/valid-title.md
Expand Up @@ -152,9 +152,10 @@ describe('foo', () => {
## Options

```ts
interface {
interface Options {
ignoreTypeOfDescribeName?: boolean;
disallowedWords?: string[];
mustMatch?: Partial<Record<'describe' | 'test' | 'it', string>> | string;
}
```

Expand All @@ -172,7 +173,7 @@ Default: `[]`
A string array of words that are not allowed to be used in test titles. Matching
is not case-sensitive, and looks for complete words:

Examples of **incorrect** code using `disallowedWords`:
Examples of **incorrect** code when using `disallowedWords`:

```js
// with disallowedWords: ['correct', 'all', 'every', 'properly']
Expand All @@ -190,3 +191,37 @@ it('correctly sets the value', () => {});
test('that everything is as it should be', () => {});
describe('the proper way to handle things', () => {});
```

#### `mustMatch`

Default: `{}`

Allows enforcing that titles must match a given Regular Expression. An object
can be provided to apply different Regular Expressions to specific Jest test
function groups (`describe`, `test`, and `it`).

Examples of **incorrect** code when using `mustMatch`:

```js
// with mustMatch: '$that'
describe('the correct way to do things', () => {});
fit('this there!', () => {});

// with mustMatch: { test: '$that' }
describe('the tests that will be run', () => {});
test('the stuff works', () => {});
xtest('errors that are thrown have messages', () => {});
```

Examples of **correct** code when using `mustMatch`:

```js
// with mustMatch: '$that'
describe('that thing that needs to be done', () => {});
fit('that this there!', () => {});

// with mustMatch: { test: '$that' }
describe('the tests that will be run', () => {});
test('that the stuff works', () => {});
xtest('that errors that thrown have messages', () => {});
```
91 changes: 91 additions & 0 deletions src/rules/__tests__/valid-title.test.ts
Expand Up @@ -100,6 +100,97 @@ ruleTester.run('disallowedWords option', rule, {
],
});

ruleTester.run('mustMatch option', rule, {
valid: [
'describe("the correct way to properly handle all the things", () => {});',
'test("that all is as it should be", () => {});',
{
code: 'it("correctly sets the value", () => {});',
options: [{ mustMatch: undefined }],
},
{
code: 'it("correctly sets the value", () => {});',
options: [{ mustMatch: / /u.source }],
},
{
code: 'it("correctly sets the value #unit", () => {});',
options: [{ mustMatch: /#(?:unit|integration|e2e)/u.source }],
},
{
code: 'it("correctly sets the value", () => {});',
options: [{ mustMatch: { test: /#(?:unit|integration|e2e)/u.source } }],
},
],
invalid: [
{
code: 'test("the correct way to properly handle all things", () => {});',
options: [{ mustMatch: /#(?:unit|integration|e2e)/u.source }],
errors: [
{
messageId: 'mustMatch',
data: {
jestFunctionName: 'test',
pattern: /#(?:unit|integration|e2e)/u.source,
},
column: 6,
line: 1,
},
],
},
{
code: 'describe("the test", () => {});',
options: [
{ mustMatch: { describe: /#(?:unit|integration|e2e)/u.source } },
],
errors: [
{
messageId: 'mustMatch',
data: {
jestFunctionName: 'describe',
pattern: /#(?:unit|integration|e2e)/u.source,
},
column: 10,
line: 1,
},
],
},
{
code: 'xdescribe("the test", () => {});',
options: [
{ mustMatch: { describe: /#(?:unit|integration|e2e)/u.source } },
],
errors: [
{
messageId: 'mustMatch',
data: {
jestFunctionName: 'describe',
pattern: /#(?:unit|integration|e2e)/u.source,
},
column: 11,
line: 1,
},
],
},
{
code: 'describe.skip("the test", () => {});',
options: [
{ mustMatch: { describe: /#(?:unit|integration|e2e)/u.source } },
],
errors: [
{
messageId: 'mustMatch',
data: {
jestFunctionName: 'describe',
pattern: /#(?:unit|integration|e2e)/u.source,
},
column: 15,
line: 1,
},
],
},
],
});

ruleTester.run('title-must-be-string', rule, {
valid: [
'it("is a string", () => {});',
Expand Down
66 changes: 60 additions & 6 deletions src/rules/valid-title.ts
Expand Up @@ -37,17 +37,33 @@ const quoteStringValue = (node: StringNode): string =>
? `\`${node.quasis[0].value.raw}\``
: node.raw;

const findMustMatchPattern = (
mustMatch: Partial<Record<string, string>> | string,
nodeName: string,
): string | null => {
const mustMatchRecord =
typeof mustMatch === 'string'
? { describe: mustMatch, test: mustMatch, it: mustMatch }
: mustMatch;

return mustMatchRecord[nodeName] || null;
};

interface Options {
ignoreTypeOfDescribeName?: boolean;
disallowedWords?: string[];
mustMatch?: Partial<Record<'describe' | 'test' | 'it', string>> | string;
}

type MessageIds =
| 'titleMustBeString'
| 'emptyTitle'
| 'duplicatePrefix'
| 'accidentalSpace'
| 'disallowedWord';
| 'disallowedWord'
| 'mustMatch';

export default createRule<
[{ ignoreTypeOfDescribeName?: boolean; disallowedWords?: string[] }],
MessageIds
>({
export default createRule<[Options], MessageIds>({
name: __filename,
meta: {
docs: {
Expand All @@ -61,6 +77,7 @@ export default createRule<
duplicatePrefix: 'should not have duplicate prefix',
accidentalSpace: 'should not have leading or trailing spaces',
disallowedWord: '"{{ word }}" is not allowed in test titles.',
mustMatch: '{{ jestFunctionName }} should match {{ pattern }}',
},
type: 'suggestion',
schema: [
Expand All @@ -75,14 +92,31 @@ export default createRule<
type: 'array',
items: { type: 'string' },
},
mustMatch: {
oneOf: [
{ type: 'string' },
{
type: 'object',
properties: {
describe: { type: 'string' },
test: { type: 'string' },
it: { type: 'string' },
},
additionalProperties: false,
},
],
},
},
additionalProperties: false,
},
],
fixable: 'code',
},
defaultOptions: [{ ignoreTypeOfDescribeName: false, disallowedWords: [] }],
create(context, [{ ignoreTypeOfDescribeName, disallowedWords = [] }]) {
create(
context,
[{ ignoreTypeOfDescribeName, disallowedWords = [], mustMatch }],
) {
const disallowedWordsRegexp = new RegExp(
`\\b(${disallowedWords.join('|')})\\b`,
'iu',
Expand Down Expand Up @@ -181,6 +215,26 @@ export default createRule<
],
});
}

if (!mustMatch) {
return;
}

const [jestFunctionName] = nodeName.split('.');
const pattern = findMustMatchPattern(mustMatch, jestFunctionName);

if (pattern) {
if (!new RegExp(pattern, 'u').test(title)) {
context.report({
messageId: 'mustMatch',
node: argument,
data: {
jestFunctionName,
pattern,
},
});
}
}
},
};
},
Expand Down

0 comments on commit a70a4cc

Please sign in to comment.