Skip to content

Commit 87ed639

Browse files
committed
Adding in type rules and tests.
1 parent 6c3bff2 commit 87ed639

File tree

10 files changed

+653
-0
lines changed

10 files changed

+653
-0
lines changed

packages/commitlint-plugin-github-rules/src/index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ import {
1414
subjectSeparatorRuleResolver,
1515
} from './rules/subject';
1616

17+
import {
18+
typeEmptyRuleResolver,
19+
typeCaseRuleResolver,
20+
typeEnumRuleResolver,
21+
typeMinLengthRuleResolver,
22+
typeMaxLengthRuleResolver,
23+
} from './rules/type';
24+
1725
const commitlintGitHubRules = utils.commitlintGitHubConstants.GITHUB_RULES;
1826

1927
export const commitlintPluginGitHub: CommitlintPluginGitHub = {
@@ -28,6 +36,12 @@ export const commitlintPluginGitHub: CommitlintPluginGitHub = {
2836
[commitlintGitHubRules.subjectMinLength]: subjectMinLengthRuleResolver,
2937
[commitlintGitHubRules.subjectMinLength]: subjectMaxLengthRuleResolver,
3038
[commitlintGitHubRules.subjectSeparator]: subjectSeparatorRuleResolver,
39+
40+
[commitlintGitHubRules.typeEmpty]: typeEmptyRuleResolver,
41+
[commitlintGitHubRules.typeCase]: typeCaseRuleResolver,
42+
[commitlintGitHubRules.typeEnum]: typeEnumRuleResolver,
43+
[commitlintGitHubRules.typeMinLength]: typeMinLengthRuleResolver,
44+
[commitlintGitHubRules.typeMinLength]: typeMaxLengthRuleResolver,
3145
},
3246
};
3347

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { ParsedCommitMessage } from 'commitlint-github-utils';
2+
3+
import { BaseParsedCommit, RuleResolverResult, RuleResolver } from '../../../@types';
4+
import resolveRuleUsingBaseResolver from '../utils/wrappedRuleResolver';
5+
6+
export function typeAdapter(parsed: ParsedCommitMessage): BaseParsedCommit {
7+
return { type: parsed.type };
8+
}
9+
10+
export function typeRuleResolver<T>(baseRuleResolver: RuleResolver<T>): RuleResolver<T> {
11+
return (parsed, when, value): RuleResolverResult =>
12+
resolveRuleUsingBaseResolver(baseRuleResolver, typeAdapter, parsed, when, value);
13+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// FIXME: Temporarily disabling TypeScript checking on importing base rules
2+
// until @commitlint/rules exports index.d.ts which should be soon:
3+
// See: https://github.com/conventional-changelog/commitlint/issues/659
4+
5+
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
6+
// @ts-ignore
7+
import baseRules from '@commitlint/rules';
8+
import utils from 'commitlint-github-utils';
9+
10+
import { RuleResolver } from '../../../@types';
11+
import { typeRuleResolver } from './helpers';
12+
13+
const commitlintGitHubRules = utils.commitlintGitHubConstants.GITHUB_RULES;
14+
15+
// Type Rules
16+
17+
export const typeEmptyRuleResolver: RuleResolver<unknown> = typeRuleResolver(
18+
baseRules[commitlintGitHubRules.typeEmpty],
19+
);
20+
21+
export const typeCaseRuleResolver: RuleResolver<unknown> = typeRuleResolver(baseRules[commitlintGitHubRules.typeCase]);
22+
23+
export const typeEnumRuleResolver: RuleResolver<unknown> = typeRuleResolver(baseRules[commitlintGitHubRules.typeEnum]);
24+
25+
export const typeMinLengthRuleResolver: RuleResolver<unknown> = typeRuleResolver(
26+
baseRules[commitlintGitHubRules.typeMinLength],
27+
);
28+
29+
export const typeMaxLengthRuleResolver: RuleResolver<unknown> = typeRuleResolver(
30+
baseRules[commitlintGitHubRules.typeMaxLength],
31+
);
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// See comment in typeRuleResolvers.ts for why these checks are disabled:
2+
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
3+
// @ts-ignore
4+
import baseRules from '@commitlint/rules';
5+
import { When } from 'commitlint-github-utils/@types';
6+
import utils from 'commitlint-github-utils';
7+
8+
import { BaseParsedCommit, RuleResolverResult, RuleResolver } from '../../../../@types';
9+
import { typeAdapter } from '../helpers';
10+
import * as typeRuleResolvers from '../index';
11+
12+
import resolveRuleUsingBaseResolver from '../../utils/wrappedRuleResolver';
13+
14+
// Mock the resolveRuleUsingBaseResolver so we can isolate our test to just the function-under-test: typeRuleResolver
15+
jest.mock('../../utils/wrappedRuleResolver');
16+
17+
const mockedResolveRuleUsingBaseResolver: jest.Mock<RuleResolverResult> = resolveRuleUsingBaseResolver as jest.Mock<
18+
RuleResolverResult
19+
>;
20+
21+
const commitlintGitHubRules = utils.commitlintGitHubConstants.GITHUB_RULES;
22+
23+
const EXPECTED_PARSED: BaseParsedCommit = { raw: 'dummy-raw' };
24+
const EXPECTED_VALUE = 'dummy-value';
25+
26+
function validateDelegateRuleResolver(
27+
delegateRuleName: string,
28+
typeRuleResolver: RuleResolver<any>,
29+
expectedWhen: When,
30+
): void {
31+
// Given that the standard type rule exists
32+
const delegate = baseRules[delegateRuleName];
33+
expect(delegate).toBeDefined();
34+
35+
// And that the wrapped type rule under test exists
36+
expect(typeRuleResolver).toBeDefined();
37+
38+
// And we mock the base rule resolving function to return a mock response
39+
const mockResult: RuleResolverResult = [true, delegateRuleName];
40+
mockedResolveRuleUsingBaseResolver.mockClear();
41+
mockedResolveRuleUsingBaseResolver.mockReturnValue(mockResult);
42+
43+
// When we call the type rule under test
44+
const result = typeRuleResolver(EXPECTED_PARSED, expectedWhen, EXPECTED_VALUE);
45+
46+
// Then we expect the mock response from the base rule resolving function to be returned
47+
expect(result).toEqual(mockResult);
48+
49+
// And we expect the base rule resolving function with the correct arguments, including the converted when
50+
expect(mockedResolveRuleUsingBaseResolver).toHaveBeenCalledWith(
51+
delegate,
52+
typeAdapter,
53+
EXPECTED_PARSED,
54+
expectedWhen,
55+
EXPECTED_VALUE,
56+
);
57+
58+
// The base rule resolving function itself is tested by wrappedRuleResolver.test.ts
59+
// We also have black box tests in this directory
60+
}
61+
62+
describe('delegate type rule resolvers', () => {
63+
it('typeEmptyRuleResolver should delegate correctly for non-WIP commits only', () => {
64+
validateDelegateRuleResolver(commitlintGitHubRules.typeEmpty, typeRuleResolvers.typeEmptyRuleResolver, When.ALWAYS);
65+
validateDelegateRuleResolver(commitlintGitHubRules.typeEmpty, typeRuleResolvers.typeEmptyRuleResolver, When.NEVER);
66+
67+
validateDelegateRuleResolver(
68+
commitlintGitHubRules.typeEmpty,
69+
typeRuleResolvers.typeEmptyRuleResolver,
70+
When.IGNORED,
71+
);
72+
});
73+
74+
it('typeCaseRuleResolver should delegate correctly for non-WIP commits only', () => {
75+
validateDelegateRuleResolver(commitlintGitHubRules.typeCase, typeRuleResolvers.typeCaseRuleResolver, When.ALWAYS);
76+
validateDelegateRuleResolver(commitlintGitHubRules.typeCase, typeRuleResolvers.typeCaseRuleResolver, When.NEVER);
77+
validateDelegateRuleResolver(commitlintGitHubRules.typeCase, typeRuleResolvers.typeCaseRuleResolver, When.IGNORED);
78+
});
79+
80+
it('typeEnumRuleResolver should delegate correctly for non-WIP commits only, using explicit value if passed', () => {
81+
validateDelegateRuleResolver(commitlintGitHubRules.typeEnum, typeRuleResolvers.typeEnumRuleResolver, When.ALWAYS);
82+
validateDelegateRuleResolver(commitlintGitHubRules.typeEnum, typeRuleResolvers.typeEnumRuleResolver, When.NEVER);
83+
validateDelegateRuleResolver(commitlintGitHubRules.typeEnum, typeRuleResolvers.typeEnumRuleResolver, When.IGNORED);
84+
});
85+
86+
it('typeMinLengthRuleResolver should delegate correctly for non-WIP commits only', () => {
87+
validateDelegateRuleResolver(
88+
commitlintGitHubRules.typeMinLength,
89+
typeRuleResolvers.typeMinLengthRuleResolver,
90+
When.ALWAYS,
91+
);
92+
validateDelegateRuleResolver(
93+
commitlintGitHubRules.typeMinLength,
94+
typeRuleResolvers.typeMinLengthRuleResolver,
95+
When.NEVER,
96+
);
97+
validateDelegateRuleResolver(
98+
commitlintGitHubRules.typeMinLength,
99+
typeRuleResolvers.typeMinLengthRuleResolver,
100+
When.IGNORED,
101+
);
102+
});
103+
104+
it('typeMaxLengthRuleResolver should delegate correctly for non-WIP commits only', () => {
105+
validateDelegateRuleResolver(
106+
commitlintGitHubRules.typeMaxLength,
107+
typeRuleResolvers.typeMaxLengthRuleResolver,
108+
When.ALWAYS,
109+
);
110+
validateDelegateRuleResolver(
111+
commitlintGitHubRules.typeMaxLength,
112+
typeRuleResolvers.typeMaxLengthRuleResolver,
113+
When.NEVER,
114+
);
115+
validateDelegateRuleResolver(
116+
commitlintGitHubRules.typeMaxLength,
117+
typeRuleResolvers.typeMaxLengthRuleResolver,
118+
When.IGNORED,
119+
);
120+
});
121+
});
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { ParsedCommitMessage, When } from 'commitlint-github-utils/@types';
2+
import { RuleResolver, RuleResolverResult, BaseParsedCommit } from '../../../../@types';
3+
import { typeAdapter, typeRuleResolver } from '../helpers';
4+
5+
import resolveRuleUsingBaseResolver from '../../utils/wrappedRuleResolver';
6+
7+
// Mock the resolveRuleUsingBaseResolver so we can isolate our test to just the function-under-test: typeRuleResolver
8+
jest.mock('../../utils/wrappedRuleResolver');
9+
10+
const mockedResolveRuleUsingBaseResolver: jest.Mock<RuleResolverResult> = resolveRuleUsingBaseResolver as jest.Mock<
11+
RuleResolverResult
12+
>;
13+
14+
const EXPECTED_PARSED: BaseParsedCommit = { raw: 'dummy-raw' };
15+
const EXPECTED_VALUE = 'dummy-value';
16+
17+
const BASE_RESOLVER: RuleResolver<string> = jest.fn();
18+
const RESULT_FROM_BASE_RESOLVER: RuleResolverResult = [true, 'DUMMY_RESULT'];
19+
20+
mockedResolveRuleUsingBaseResolver.mockReturnValue(RESULT_FROM_BASE_RESOLVER);
21+
22+
describe('typeAdapter', () => {
23+
it('should return the type from the ParsedCommitMessage', () => {
24+
const type = 'expected-type';
25+
const commit: ParsedCommitMessage = {
26+
issueNumbers: [],
27+
isWip: false,
28+
body: [],
29+
type,
30+
};
31+
32+
expect(typeAdapter(commit)).toEqual({ type });
33+
});
34+
});
35+
36+
describe('typeRuleResolver', () => {
37+
const ruleResolver: RuleResolver<string> = typeRuleResolver(BASE_RESOLVER);
38+
39+
// See comment in the typeRuleResolver() method for why the when clause gets converted to the NON_WIPS equivalent
40+
it('should use the arguments passed, converting the "when" to the NON_WIPS equivalent', () => {
41+
// ALWAYS
42+
expect(ruleResolver(EXPECTED_PARSED, When.ALWAYS, EXPECTED_VALUE)).toEqual(RESULT_FROM_BASE_RESOLVER);
43+
44+
expect(mockedResolveRuleUsingBaseResolver).toHaveBeenCalledWith(
45+
BASE_RESOLVER,
46+
typeAdapter,
47+
EXPECTED_PARSED,
48+
When.ALWAYS,
49+
EXPECTED_VALUE,
50+
);
51+
52+
// IGNORED is treated as NON_WIPS_ALWAYS as it's arbitrary
53+
expect(ruleResolver(EXPECTED_PARSED, When.IGNORED, EXPECTED_VALUE)).toEqual(RESULT_FROM_BASE_RESOLVER);
54+
55+
expect(mockedResolveRuleUsingBaseResolver).toHaveBeenCalledWith(
56+
BASE_RESOLVER,
57+
typeAdapter,
58+
EXPECTED_PARSED,
59+
When.IGNORED,
60+
EXPECTED_VALUE,
61+
);
62+
63+
// NEVER
64+
expect(ruleResolver(EXPECTED_PARSED, When.NEVER, EXPECTED_VALUE)).toEqual(RESULT_FROM_BASE_RESOLVER);
65+
66+
expect(mockedResolveRuleUsingBaseResolver).toHaveBeenCalledWith(
67+
BASE_RESOLVER,
68+
typeAdapter,
69+
EXPECTED_PARSED,
70+
When.NEVER,
71+
EXPECTED_VALUE,
72+
);
73+
});
74+
});
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// import { TargetCaseType } from '@commitlint/types'; // When it is published
2+
import { When, TargetCaseType } from 'commitlint-github-utils/@types';
3+
import { WhenAndRequiredValue, RuleResolverResult } from '../../../../@types';
4+
import { typeCaseRuleResolver } from '../index';
5+
import runRule from '../../utils/tests/utils';
6+
7+
const parse = (
8+
whenAndValue: WhenAndRequiredValue<TargetCaseType | TargetCaseType[]>,
9+
rawCommitMessage: string,
10+
): RuleResolverResult => runRule(typeCaseRuleResolver, whenAndValue, rawCommitMessage);
11+
12+
describe('typeCaseRuleResolver', () => {
13+
it('should always return an error response if an empty commit message is provided', () => {
14+
expect(parse([When.ALWAYS, 'lower-case'], '')[0]).toEqual(false);
15+
expect(parse([When.NEVER, 'lower-case'], '')[0]).toEqual(false);
16+
});
17+
18+
it('should return a success response when passed non-WIP commits with no type at all, as nothing to validate', () => {
19+
expect(parse([When.ALWAYS, 'lower-case'], 'my commit message')[0]).toEqual(true);
20+
expect(parse([When.ALWAYS, 'lower-case'], '(#1) my commit message')[0]).toEqual(true);
21+
});
22+
23+
it('should return a success response when passed non-WIP commits with a lowercased type matching the requirement', () => {
24+
expect(parse([When.ALWAYS, 'lower-case'], '(#1) chore: my commit message')[0]).toEqual(true);
25+
26+
expect(parse([When.ALWAYS, 'lower-case'], '(#1, #2) fix: my commit message')[0]).toEqual(true);
27+
expect(parse([When.ALWAYS, 'lower-case'], '(#1,#2) arbitrary: my commit message')[0]).toEqual(true);
28+
29+
expect(parse([When.ALWAYS, 'lower-case'], '(#123, #23) dummy: my commit message')[0]).toEqual(true);
30+
expect(parse([When.ALWAYS, 'lower-case'], '(#123,#23) chore: my commit message')[0]).toEqual(true);
31+
});
32+
33+
it('should return a success response when passed non-WIP commits with a uppercased type matching the requirement', () => {
34+
expect(parse([When.ALWAYS, 'upper-case'], '(#1) CHORE: my commit message')[0]).toEqual(true);
35+
36+
expect(parse([When.ALWAYS, 'upper-case'], '(#1, #2) FIX: my commit message')[0]).toEqual(true);
37+
expect(parse([When.ALWAYS, 'upper-case'], '(#1,#2) ARBITRARY: my commit message')[0]).toEqual(true);
38+
39+
expect(parse([When.ALWAYS, 'upper-case'], '(#123, #23) DUMMY: my commit message')[0]).toEqual(true);
40+
expect(parse([When.ALWAYS, 'upper-case'], '(#123,#23) CHORE: my commit message')[0]).toEqual(true);
41+
});
42+
43+
it('should return an error response when passed non-WIP commits with a lowercased type and never requiring it', () => {
44+
expect(parse([When.NEVER, 'lower-case'], '(#1) chore: my commit message')[0]).toEqual(false);
45+
46+
expect(parse([When.NEVER, 'lower-case'], '(#1, #2) fix: my commit message')[0]).toEqual(false);
47+
expect(parse([When.NEVER, 'lower-case'], '(#1,#2) arbitrary: my commit message')[0]).toEqual(false);
48+
49+
expect(parse([When.NEVER, 'lower-case'], '(#123, #23) dummy: my commit message')[0]).toEqual(false);
50+
expect(parse([When.NEVER, 'lower-case'], '(#123,#23) chore: my commit message')[0]).toEqual(false);
51+
});
52+
53+
it('should return an error response when passed non-WIP commits with a uppercased type and never requiring it', () => {
54+
expect(parse([When.NEVER, 'upper-case'], '(#1) CHORE: my commit message')[0]).toEqual(false);
55+
56+
expect(parse([When.NEVER, 'upper-case'], '(#1, #2) FIX: my commit message')[0]).toEqual(false);
57+
expect(parse([When.NEVER, 'upper-case'], '(#1,#2) ARBITRARY: my commit message')[0]).toEqual(false);
58+
59+
expect(parse([When.NEVER, 'upper-case'], '(#123, #23) DUMMY: my commit message')[0]).toEqual(false);
60+
expect(parse([When.NEVER, 'upper-case'], '(#123,#23) CHORE: my commit message')[0]).toEqual(false);
61+
});
62+
63+
it('should return a success response when passed WIP commits as that is not treated as a regular type and so is not validated by this rule', () => {
64+
// ALWAYS
65+
expect(parse([When.ALWAYS, 'lower-case'], '(#1) WIP')[0]).toEqual(true);
66+
67+
expect(parse([When.ALWAYS, 'lower-case'], '(#1) WIP:my commit message')[0]).toEqual(true);
68+
expect(parse([When.ALWAYS, 'lower-case'], '(#1) WIP: my commit message')[0]).toEqual(true);
69+
70+
expect(parse([When.ALWAYS, 'lower-case'], '(#1, #2, #3, #4) WIP: my commit message')[0]).toEqual(true);
71+
expect(parse([When.ALWAYS, 'lower-case'], '(#1, #2, #3, #4) WIP:my commit message')[0]).toEqual(true);
72+
73+
expect(parse([When.ALWAYS, 'lower-case'], '(#1,#2,#3,#4) WIP: my commit message')[0]).toEqual(true);
74+
expect(parse([When.ALWAYS, 'lower-case'], '(#1,#2,#3,#4) WIP:my commit message')[0]).toEqual(true);
75+
76+
expect(parse([When.ALWAYS, 'lower-case'], 'WIP2: My commit message')[0]).toEqual(true);
77+
expect(parse([When.ALWAYS, 'lower-case'], 'WIP 2: My commit message')[0]).toEqual(true);
78+
expect(parse([When.ALWAYS, 'lower-case'], 'WIP2 - My commit message')[0]).toEqual(true);
79+
expect(parse([When.ALWAYS, 'lower-case'], 'WIP 2 - My commit message')[0]).toEqual(true);
80+
81+
// NEVER
82+
expect(parse([When.NEVER, 'lower-case'], '(#1) WIP')[0]).toEqual(true);
83+
84+
expect(parse([When.NEVER, 'lower-case'], '(#1) WIP:my commit message')[0]).toEqual(true);
85+
expect(parse([When.NEVER, 'lower-case'], '(#1) WIP: my commit message')[0]).toEqual(true);
86+
87+
expect(parse([When.NEVER, 'lower-case'], '(#1, #2, #3, #4) WIP: my commit message')[0]).toEqual(true);
88+
expect(parse([When.NEVER, 'lower-case'], '(#1, #2, #3, #4) WIP:my commit message')[0]).toEqual(true);
89+
90+
expect(parse([When.NEVER, 'lower-case'], '(#1,#2,#3,#4) WIP: my commit message')[0]).toEqual(true);
91+
expect(parse([When.NEVER, 'lower-case'], '(#1,#2,#3,#4) WIP:my commit message')[0]).toEqual(true);
92+
93+
expect(parse([When.NEVER, 'lower-case'], 'WIP2: My commit message')[0]).toEqual(true);
94+
expect(parse([When.NEVER, 'lower-case'], 'WIP 2: My commit message')[0]).toEqual(true);
95+
expect(parse([When.NEVER, 'lower-case'], 'WIP2 - My commit message')[0]).toEqual(true);
96+
expect(parse([When.NEVER, 'lower-case'], 'WIP 2 - My commit message')[0]).toEqual(true);
97+
});
98+
});

0 commit comments

Comments
 (0)