From 1a8db80329a93b4a087620aae711bab205769e78 Mon Sep 17 00:00:00 2001 From: Jeroen de Bruijn Date: Sun, 6 Sep 2020 09:49:58 +0200 Subject: [PATCH] feat: add function rule plugin sources and tests --- package-lock.json | 3 +- package.json | 14 +++++- src/function-rule.test.ts | 95 +++++++++++++++++++++++++++++++++++++++ src/function-rule.ts | 26 +++++++++++ src/index.test.ts | 14 ++++++ src/index.ts | 13 ++++++ src/plugin.test.ts | 13 ++++++ src/plugin.ts | 7 +++ src/rules.test.ts | 31 +++++++++++++ src/rules.ts | 42 +++++++++++++++++ 10 files changed, 254 insertions(+), 4 deletions(-) create mode 100644 src/function-rule.test.ts create mode 100644 src/function-rule.ts create mode 100644 src/index.test.ts create mode 100644 src/index.ts create mode 100644 src/plugin.test.ts create mode 100644 src/plugin.ts create mode 100644 src/rules.test.ts create mode 100644 src/rules.ts diff --git a/package-lock.json b/package-lock.json index 78556f1..84bd83c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -821,8 +821,7 @@ "@commitlint/types": { "version": "9.1.2", "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-9.1.2.tgz", - "integrity": "sha512-r3fwVbVH+M8W0qYlBBZFsUwKe6NT5qvz+EmU7sr8VeN1cQ63z+3cfXyTo7WGGEMEgKiT0jboNAK3b1FZp8k9LQ==", - "dev": true + "integrity": "sha512-r3fwVbVH+M8W0qYlBBZFsUwKe6NT5qvz+EmU7sr8VeN1cQ63z+3cfXyTo7WGGEMEgKiT0jboNAK3b1FZp8k9LQ==" }, "@eslint/eslintrc": { "version": "0.1.3", diff --git a/package.json b/package.json index d7f28e6..6a14903 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,11 @@ "name": "commitlint-plugin-function-rules", "version": "1.0.0", "description": "Commitlint plugin to define rules as functions.", - "private": true, + "main": "dist/index.js", + "types": "dist/index.d.js", + "files": [ + "dist/**/!(*.test).{js,d.ts}" + ], "scripts": { "lint": "run-p format:check lint-es", "lint:fix": "run-s format lint-es:fix", @@ -34,10 +38,16 @@ "url": "https://github.com/vidavidorra/commitlint-plugin-function-rules/issues" }, "homepage": "https://github.com/vidavidorra/commitlint-plugin-function-rules#readme", - "dependencies": {}, + "peerDependencies": { + "@commitlint/lint": "9.1.2" + }, + "dependencies": { + "@commitlint/types": "9.1.2" + }, "devDependencies": { "@commitlint/cli": "9.1.2", "@commitlint/config-conventional": "9.1.2", + "@commitlint/rules": "9.1.2", "@jest/globals": "26.4.2", "@semantic-release/changelog": "5.0.1", "@semantic-release/git": "9.0.0", diff --git a/src/function-rule.test.ts b/src/function-rule.test.ts new file mode 100644 index 0000000..a8f6eea --- /dev/null +++ b/src/function-rule.test.ts @@ -0,0 +1,95 @@ +import { Commit, RuleConfigCondition, RuleOutcome } from '@commitlint/types'; +import { beforeEach, describe, expect, it, jest } from '@jest/globals'; +import functionRule, { FunctionRule } from './function-rule'; + +describe('functionRule', (): void => { + const commit: Commit = { + type: 'chore', + scope: 'scope', + subject: 'test', + merge: null, + header: 'chore(scope): test', + body: null, + footer: null, + notes: [], + references: [], + mentions: [], + revert: null, + raw: 'chore(scope): test\n', + }; + const when: RuleConfigCondition = 'always'; + /** + * To pass this to an function, that is obviously not expecting a mock, a type + * assertion is needed. For this, the as-syntax is needed when a mock is + * passed as function argument. + */ + const ruleImplementation = jest.fn(); + + beforeEach(() => { + ruleImplementation.mockReset(); + }); + + it('calls implementation function', () => { + functionRule(commit, when, ruleImplementation as FunctionRule); + + expect(ruleImplementation).toHaveBeenCalledTimes(1); + }); + + it('passes arguments to implementation function', () => { + functionRule(commit, when, ruleImplementation as FunctionRule); + + expect(ruleImplementation).toHaveBeenCalledWith(commit, when); + }); + + it("defaults 'when' argument to 'always'", () => { + functionRule(commit, undefined, ruleImplementation as FunctionRule); + + expect(ruleImplementation).toHaveBeenCalledWith(commit, 'always'); + }); + + it('returns value from sync implementation function', () => { + const returnValue: RuleOutcome = [ + true, + 'Message from sync implementation function.', + ]; + ruleImplementation.mockImplementation(() => returnValue); + const value = functionRule( + commit, + when, + ruleImplementation as FunctionRule, + ); + + expect(ruleImplementation).toHaveBeenCalledTimes(1); + expect(value).toEqual(returnValue); + }); + + it('returns value from async implementation function', async () => { + const returnValue: RuleOutcome = [ + true, + 'Message from async implementation function.', + ]; + ruleImplementation.mockImplementation(() => Promise.resolve(returnValue)); + const value = await functionRule( + commit, + when, + ruleImplementation as FunctionRule, + ); + + expect(ruleImplementation).toHaveBeenCalledTimes(1); + expect(value).toEqual(returnValue); + }); + + it("throws an error when 'value' is 'undefined'", () => { + expect(() => { + functionRule(commit, when, undefined); + }).toThrow(); + }); + + it("throws an error when 'value' is 'not an function'", () => { + expect(() => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error: TS2345: Argument of type ... is not assignable to parameter of type ... + functionRule(commit, when, 'not a function!'); + }).toThrow(); + }); +}); diff --git a/src/function-rule.ts b/src/function-rule.ts new file mode 100644 index 0000000..5310763 --- /dev/null +++ b/src/function-rule.ts @@ -0,0 +1,26 @@ +import { + Commit, + Rule, + RuleConfigCondition, + RuleOutcome, +} from '@commitlint/types'; + +type FunctionRule = ( + parsed: Commit, + when: RuleConfigCondition, +) => RuleOutcome | Promise; + +const functionRule: Rule = ( + parsed: Commit, + when: RuleConfigCondition = 'always', + value: FunctionRule | undefined, +) => { + if (typeof value === 'function') { + return value(parsed, when); + } + + throw new Error('Not a valid function!'); +}; + +export default functionRule; +export { FunctionRule }; diff --git a/src/index.test.ts b/src/index.test.ts new file mode 100644 index 0000000..3563236 --- /dev/null +++ b/src/index.test.ts @@ -0,0 +1,14 @@ +import * as plugin from './index'; +import { describe, expect, it } from '@jest/globals'; +import rules from './rules'; + +describe('index', () => { + it(`exports a CommonJS module with 'rules' object`, () => { + expect(Object.keys(plugin)).toEqual(['rules']); + expect(typeof plugin.rules).toEqual('object'); + }); + + it('exports rules', () => { + expect(plugin).toMatchObject({ rules }); + }); +}); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..e50d5b8 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,13 @@ +import Plugin from './plugin'; +import rules from './rules'; + +const plugin: Plugin = { + rules, +}; + +/** + * Export single object, according to the CommonJS model. The CommonJS module is + * explicitly used here as that's the kind of module commitlint requires for + * plugins. + */ +export = plugin; diff --git a/src/plugin.test.ts b/src/plugin.test.ts new file mode 100644 index 0000000..9da1a6c --- /dev/null +++ b/src/plugin.test.ts @@ -0,0 +1,13 @@ +import { describe, expect, it } from '@jest/globals'; +import Plugin from './plugin'; + +describe('Plugin', (): void => { + it("defines 'rules' as an object", () => { + const plugin: Plugin = { + rules: {}, + }; + + expect(Object.keys(plugin)).toEqual(['rules']); + expect(typeof plugin.rules).toEqual('object'); + }); +}); diff --git a/src/plugin.ts b/src/plugin.ts new file mode 100644 index 0000000..4d2d207 --- /dev/null +++ b/src/plugin.ts @@ -0,0 +1,7 @@ +import { Rules } from './rules'; + +interface Plugin { + rules: Rules; +} + +export default Plugin; diff --git a/src/rules.test.ts b/src/rules.test.ts new file mode 100644 index 0000000..26c7281 --- /dev/null +++ b/src/rules.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from '@jest/globals'; +import commitlintRules from '@commitlint/rules'; +import functionRule from './function-rule'; +import rules from './rules'; + +describe('rules', (): void => { + const prefix = 'function-rules/'; + const names = Object.keys(rules); + const commitlintRuleNames = Object.keys(commitlintRules).sort(); + + it('exports the same rules as commitlint', () => { + const strippedNames = names.map((e) => { + return e.replace(new RegExp(`^${prefix}`), ''); + }); + + expect(strippedNames).toEqual(commitlintRuleNames); + }); + + it(`are exported with with '${prefix}' as prefix`, () => { + const everyNameHasPrefix = names.every((e) => e.startsWith(prefix)); + + expect(everyNameHasPrefix).toBe(true); + }); + + it(`have 'functionRule' as value`, () => { + const values = Object.values(rules); + const everyValueIsFunctionRule = values.every((e) => e === functionRule); + + expect(everyValueIsFunctionRule).toBe(true); + }); +}); diff --git a/src/rules.ts b/src/rules.ts new file mode 100644 index 0000000..e735d62 --- /dev/null +++ b/src/rules.ts @@ -0,0 +1,42 @@ +import functionRule, { FunctionRule } from './function-rule'; +import { Rule } from '@commitlint/types'; + +type Rules = Record>; + +const rules: Rules = { + 'function-rules/body-case': functionRule, + 'function-rules/body-empty': functionRule, + 'function-rules/body-leading-blank': functionRule, + 'function-rules/body-max-length': functionRule, + 'function-rules/body-max-line-length': functionRule, + 'function-rules/body-min-length': functionRule, + 'function-rules/footer-empty': functionRule, + 'function-rules/footer-leading-blank': functionRule, + 'function-rules/footer-max-length': functionRule, + 'function-rules/footer-max-line-length': functionRule, + 'function-rules/footer-min-length': functionRule, + 'function-rules/header-case': functionRule, + 'function-rules/header-full-stop': functionRule, + 'function-rules/header-max-length': functionRule, + 'function-rules/header-min-length': functionRule, + 'function-rules/references-empty': functionRule, + 'function-rules/scope-case': functionRule, + 'function-rules/scope-empty': functionRule, + 'function-rules/scope-enum': functionRule, + 'function-rules/scope-max-length': functionRule, + 'function-rules/scope-min-length': functionRule, + 'function-rules/signed-off-by': functionRule, + 'function-rules/subject-case': functionRule, + 'function-rules/subject-empty': functionRule, + 'function-rules/subject-full-stop': functionRule, + 'function-rules/subject-max-length': functionRule, + 'function-rules/subject-min-length': functionRule, + 'function-rules/type-case': functionRule, + 'function-rules/type-empty': functionRule, + 'function-rules/type-enum': functionRule, + 'function-rules/type-max-length': functionRule, + 'function-rules/type-min-length': functionRule, +}; + +export default rules; +export { Rules };