From 93fa8cc6dd1241ec5526e9698f47f9847a8d02ee Mon Sep 17 00:00:00 2001 From: JPeer264 Date: Wed, 27 Mar 2019 12:25:04 +0100 Subject: [PATCH 1/3] Feat: add custom type --- .sgcrc | 7 +++ lib/helpers/formatters.js | 3 +- lib/questions.js | 45 +++++++++++++++- test/helper/formatters.js | 9 ++++ test/questions.js | 106 ++++++++++++++++++++++++++++++++------ 5 files changed, 152 insertions(+), 18 deletions(-) diff --git a/.sgcrc b/.sgcrc index ea05431..ed702d4 100644 --- a/.sgcrc +++ b/.sgcrc @@ -9,6 +9,13 @@ "message": "Initial commit" }, "types": [ + { + "emoji": ":wrench:", + "type": false, + "description": "A custom type with prefix", + "prefix": "SGC-", + "argKeys": ["cu", "custom"] + }, { "emoji": ":wrench:", "type": "Chore:", diff --git a/lib/helpers/formatters.js b/lib/helpers/formatters.js index 464bbf0..9f2c9f3 100644 --- a/lib/helpers/formatters.js +++ b/lib/helpers/formatters.js @@ -25,7 +25,8 @@ export const formatMessage = (answers, argv) => { ...argv, }; - const type = combineTypeScope(combinedAnswers.type, combinedAnswers.scope); + const correctType = combinedAnswers.customType || combinedAnswers.type; + const type = combineTypeScope(correctType, combinedAnswers.scope); const formattedMessage = `${type} ${(combinedAnswers.message || '').trim()}`; const result = answers.body ? combinedAnswers.editor : formattedMessage; diff --git a/lib/questions.js b/lib/questions.js index 16ba66b..3cf563c 100644 --- a/lib/questions.js +++ b/lib/questions.js @@ -2,12 +2,24 @@ import chalk from 'chalk'; import ruleWarningMessages from './rules/ruleWarningMessages'; import { formatMessage } from './helpers/formatters'; +const customName = 'Custom'; + const choices = (config) => { const choicesList = []; + let customCount = 1; + config.types.forEach((type) => { const emoji = config.emoji && type.emoji ? `${type.emoji} ` : ''; - const configType = config.lowercaseTypes ? type.type.toLowerCase() : type.type; + let changedType = type.type; + + // type = false === it is a custom type + if (!changedType) { + changedType = `${customName} ${customCount}`; + customCount += 1; + } + + const configType = config.lowercaseTypes ? changedType.toLowerCase() : changedType; const description = type.description || ''; const argKeys = type.argKeys || []; const isArray = Array.isArray(argKeys); @@ -75,6 +87,36 @@ const questions = (config, argv = {}) => { message: 'Select the type of your commit:', choices: choicesList, }, + { + type: 'input', + name: 'customType', + when: answers => (answers.type.includes(customName) && !modifiedArgv.c), + filter: (answer, answers) => { + let customCount = 1; + + const typeChoice = config.types.find((type) => { + // if there is no type it is a custom type + if (!type.type) { + if (answers.type === `${customName} ${customCount}`) { + return true; + } + + customCount += 1; + } + + return false; + }); + + + if (!typeChoice) { + return answer; + } + + return `${typeChoice.prefix || ''}${answer}`; + }, + message: 'Choose your custom commit:', + choices: choicesList, + }, { type: 'input', name: 'scope', @@ -124,6 +166,7 @@ const questions = (config, argv = {}) => { export default questions; export { choices, + customName, initMessage, initQuestion, }; diff --git a/test/helper/formatters.js b/test/helper/formatters.js index 7852e1a..b53905e 100644 --- a/test/helper/formatters.js +++ b/test/helper/formatters.js @@ -41,6 +41,15 @@ test('FORMATMESSAGE | should format message', (t) => { t.is(message, 'myType: something'); }); +test('FORMATMESSAGE | should format message with customType', (t) => { + const message = formatMessage({ + message: ' something ', + customType: 'custom', + }); + + t.is(message, 'custom: something'); +}); + test('FORMATMESSAGE | should format with scope', (t) => { const message = formatMessage({ type: 'myType', diff --git a/test/questions.js b/test/questions.js index f495a95..d113036 100644 --- a/test/questions.js +++ b/test/questions.js @@ -9,6 +9,7 @@ import { withEmoji, withoutEmoji } from './fixtures/questions'; import getConfig from '../lib/getConfig'; import questions, { choices, + customName, initMessage, initQuestion, } from '../lib/questions'; @@ -24,6 +25,15 @@ const randomString = Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0 let globalExist = false; +const questionsListOrder = { + type: 0, + customType: 1, + scope: 2, + message: 3, + body: 4, + editor: 5, +}; + // rename global .sgcrc test.before(() => { // rename global config @@ -63,6 +73,19 @@ test('choices are rendered with emoji (default)', (t) => { t.deepEqual(choicesList, withEmoji); }); +test('choices are rendered as custom type', (t) => { + const sgc = getConfig(); + + sgc.emoji = false; + sgc.types[0].type = false; + sgc.types[1].type = false; + + const choicesList = choices(sgc); + + t.deepEqual(choicesList[0].value, `${customName} 1`); + t.deepEqual(choicesList[1].value, `${customName} 2`); +}); + test('check the values of the question object', (t) => { const config = getConfig(); const questionsList = questions(config); @@ -132,28 +155,79 @@ test('TYPE | just show if type has not been added', (t) => { const config = getConfig(); const questionsList = questions(config); - t.is(questionsList[0].when(), true); + t.is(questionsList[questionsListOrder.type].when(), true); }); test('TYPE | not show if type has been added', (t) => { const config = getConfig(); const questionsList = questions(config, { t: 'feat' }); - t.is(questionsList[0].when(), false); + t.is(questionsList[questionsListOrder.type].when(), false); }); test('SCOPE | check if scope is off by default', (t) => { const config = getConfig(); const questionsList = questions(config); - t.is(questionsList[1].when(), false); + t.is(questionsList[questionsListOrder.scope].when(), false); +}); + +test('CUSTOMTYPE | check if customType gets shown when type is defined', (t) => { + const config = getConfig(); + const questionsList = questions(config); + + t.is(questionsList[questionsListOrder.customType].when({ type: 'Feat:' }), false); + t.is(questionsList[questionsListOrder.customType].when({ type: 'anything' }), false); +}); + +test('CUSTOMTYPE | check if customType gets shown when type is custom', (t) => { + const config = getConfig(); + const questionsList = questions(config); + + t.is(questionsList[questionsListOrder.customType].when({ type: customName }), true); + t.is(questionsList[questionsListOrder.customType].when({ type: `${customName}feat` }), true); +}); + +test('CUSTOMTYPE | should not show when argv is specified', (t) => { + const config = getConfig(); + const questionsList = questions(config, { c: 'Feat:' }); + + t.is(questionsList[questionsListOrder.customType].when({ type: customName }), false); + t.is(questionsList[questionsListOrder.customType].when({ type: `${customName}feat` }), false); +}); + +test('CUSTOMTYPE | return prefixed answer', (t) => { + const config = getConfig(); + + config.types[0].prefix = 'myprefix'; + + const questionsList = questions(config); + + t.is(questionsList[questionsListOrder.customType].filter('answer', { type: `${customName} 1` }), 'myprefixanswer'); +}); + +test('CUSTOMTYPE | return nonprefixed answer', (t) => { + const config = getConfig(); + + config.types[0].prefix = undefined; + + const questionsList = questions(config); + + t.is(questionsList[questionsListOrder.customType].filter('answer', { type: `${customName} 1` }), 'answer'); +}); + +test('CUSTOMTYPE | return any type', (t) => { + const config = getConfig(); + const questionsList = questions(config); + + t.is(questionsList[questionsListOrder.customType].filter('something', { type: 'none' }), 'something'); }); test('SCOPE | check if scope is off when it has been added by argv', (t) => { const config = getConfig(); const questionsList = questions(config, { s: 'some scope' }); - t.is(questionsList[1].when(), false); + t.is(questionsList[questionsListOrder.scope].when(), false); }); test('SCOPE | check if scope is off when it has been added in config and argv', (t) => { @@ -163,7 +237,7 @@ test('SCOPE | check if scope is off when it has been added in config and argv', const questionsList = questions(config, { s: 'some scope' }); - t.is(questionsList[1].when(), false); + t.is(questionsList[questionsListOrder.scope].when(), false); }); test('SCOPE | check if scope is on when it has been added just in config', (t) => { @@ -173,53 +247,53 @@ test('SCOPE | check if scope is on when it has been added just in config', (t) = const questionsList = questions(config); - t.is(questionsList[1].when(), true); + t.is(questionsList[questionsListOrder.scope].when(), true); }); test('SCOPE | check if scope validates correctly', (t) => { const config = getConfig(); const questionsList = questions(config); - t.is(questionsList[1].validate('not correct'), 'No whitespaces allowed'); - t.is(questionsList[1].validate('correct'), true); + t.is(questionsList[questionsListOrder.scope].validate('not correct'), 'No whitespaces allowed'); + t.is(questionsList[questionsListOrder.scope].validate('correct'), true); }); test('MESSAGE | validate functions in questions', (t) => { const config = getConfig(); const questionsList = questions(config); - t.is(questionsList[2].validate('', { type: 'Fix' }), 'The commit message is not allowed to be empty'); - t.is(questionsList[2].validate('input text', { type: 'Fix' }), true); - t.is(questionsList[2].validate('This message has over 72 characters. So this test will definitely fail. I can guarantee that I am telling the truth', { type: 'Fix' }), 'The commit message is not allowed to be longer as 72 character, but is 120 character long. Consider writing a body.\n'); + t.is(questionsList[questionsListOrder.message].validate('', { type: 'Fix' }), 'The commit message is not allowed to be empty'); + t.is(questionsList[questionsListOrder.message].validate('input text', { type: 'Fix' }), true); + t.is(questionsList[questionsListOrder.message].validate('This message has over 72 characters. So this test will definitely fail. I can guarantee that I am telling the truth', { type: 'Fix' }), 'The commit message is not allowed to be longer as 72 character, but is 120 character long. Consider writing a body.\n'); }); test('MESSAGE | do not show if there is the message in argv', (t) => { const config = getConfig(); const questionsList = questions(config, { m: 'something' }); - t.is(questionsList[2].when(), false); + t.is(questionsList[questionsListOrder.message].when(), false); }); test('MESSAGE | show if no argv has been added', (t) => { const config = getConfig(); const questionsList = questions(config); - t.is(questionsList[2].when(), true); + t.is(questionsList[questionsListOrder.message].when(), true); }); test('EDITOR | when and default functions in questions', (t) => { const config = getConfig(); const questionsList = questions(config); - t.is(questionsList[4].when({ body: true }), true); - t.is(questionsList[4].when({ body: false }), false); + t.is(questionsList[questionsListOrder.editor].when({ body: true }), true); + t.is(questionsList[questionsListOrder.editor].when({ body: false }), false); }); test('EDITOR | should return formatted message', (t) => { const config = getConfig(); const questionsList = questions(config); - t.is(questionsList[4].default({ message: 'message', type: 'type' }), 'type: message\n\n\n'); + t.is(questionsList[questionsListOrder.editor].default({ message: 'message', type: 'type' }), 'type: message\n\n\n'); }); test('CONFIRM EDITOR | check if it shows if it has to', (t) => { From 40a655b4b13c3a2c79a2d75ea0a68a9d1e0dd616 Mon Sep 17 00:00:00 2001 From: JPeer264 Date: Wed, 27 Mar 2019 12:31:52 +0100 Subject: [PATCH 2/3] Chore: prevent breaking change --- .sgcrc | 7 ------- test/fixtures/.sgcrc.customType | 31 +++++++++++++++++++++++++++++++ test/questions.js | 14 +++++++------- 3 files changed, 38 insertions(+), 14 deletions(-) create mode 100644 test/fixtures/.sgcrc.customType diff --git a/.sgcrc b/.sgcrc index ed702d4..ea05431 100644 --- a/.sgcrc +++ b/.sgcrc @@ -9,13 +9,6 @@ "message": "Initial commit" }, "types": [ - { - "emoji": ":wrench:", - "type": false, - "description": "A custom type with prefix", - "prefix": "SGC-", - "argKeys": ["cu", "custom"] - }, { "emoji": ":wrench:", "type": "Chore:", diff --git a/test/fixtures/.sgcrc.customType b/test/fixtures/.sgcrc.customType new file mode 100644 index 0000000..f2fc983 --- /dev/null +++ b/test/fixtures/.sgcrc.customType @@ -0,0 +1,31 @@ +{ + "scope": false, + "body": true, + "emoji": false, + "lowercaseTypes": false, + "initialCommit": { + "isEnabled": true, + "emoji": ":tada:", + "message": "Initial commit" + }, + "types": [ + { + "emoji": ":wrench:", + "type": false, + "description": "A custom type with prefix", + "prefix": "SGC-", + "argKeys": ["cu", "custom"] + }, + { + "emoji": ":wrench:", + "type": false, + "description": "Another custom type with prefix", + "prefix": "SGGC-" + } + ], + "rules": { + "maxChar": 72, + "minChar": 10, + "endWithDot": false + } +} diff --git a/test/questions.js b/test/questions.js index d113036..e8c7bf7 100644 --- a/test/questions.js +++ b/test/questions.js @@ -74,7 +74,7 @@ test('choices are rendered with emoji (default)', (t) => { }); test('choices are rendered as custom type', (t) => { - const sgc = getConfig(); + const sgc = getConfig(path.join(fixtures, '.sgcrc.customType')); sgc.emoji = false; sgc.types[0].type = false; @@ -173,7 +173,7 @@ test('SCOPE | check if scope is off by default', (t) => { }); test('CUSTOMTYPE | check if customType gets shown when type is defined', (t) => { - const config = getConfig(); + const config = getConfig(path.join(fixtures, '.sgcrc.customType')); const questionsList = questions(config); t.is(questionsList[questionsListOrder.customType].when({ type: 'Feat:' }), false); @@ -181,7 +181,7 @@ test('CUSTOMTYPE | check if customType gets shown when type is defined', (t) => }); test('CUSTOMTYPE | check if customType gets shown when type is custom', (t) => { - const config = getConfig(); + const config = getConfig(path.join(fixtures, '.sgcrc.customType')); const questionsList = questions(config); t.is(questionsList[questionsListOrder.customType].when({ type: customName }), true); @@ -189,7 +189,7 @@ test('CUSTOMTYPE | check if customType gets shown when type is custom', (t) => { }); test('CUSTOMTYPE | should not show when argv is specified', (t) => { - const config = getConfig(); + const config = getConfig(path.join(fixtures, '.sgcrc.customType')); const questionsList = questions(config, { c: 'Feat:' }); t.is(questionsList[questionsListOrder.customType].when({ type: customName }), false); @@ -197,7 +197,7 @@ test('CUSTOMTYPE | should not show when argv is specified', (t) => { }); test('CUSTOMTYPE | return prefixed answer', (t) => { - const config = getConfig(); + const config = getConfig(path.join(fixtures, '.sgcrc.customType')); config.types[0].prefix = 'myprefix'; @@ -207,7 +207,7 @@ test('CUSTOMTYPE | return prefixed answer', (t) => { }); test('CUSTOMTYPE | return nonprefixed answer', (t) => { - const config = getConfig(); + const config = getConfig(path.join(fixtures, '.sgcrc.customType')); config.types[0].prefix = undefined; @@ -217,7 +217,7 @@ test('CUSTOMTYPE | return nonprefixed answer', (t) => { }); test('CUSTOMTYPE | return any type', (t) => { - const config = getConfig(); + const config = getConfig(path.join(fixtures, '.sgcrc.customType')); const questionsList = questions(config); t.is(questionsList[questionsListOrder.customType].filter('something', { type: 'none' }), 'something'); From 862f9a90349eb096815c74ba09c0c962dbf5941a Mon Sep 17 00:00:00 2001 From: JPeer264 Date: Wed, 27 Mar 2019 12:44:33 +0100 Subject: [PATCH 3/3] Docs: add readme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1ed9924..a222aa3 100644 --- a/README.md +++ b/README.md @@ -217,10 +217,12 @@ Example: > Types will define your git commits. If `types` is not set in your own `.sgcrc`, the `types` of the global [.sgcrc](.sgcrc) +> Notice: If the `type` is `false` it will let you to manually add the type. This is usefull especially if you have a `prefix` named `SGC-` to reference these as a ticket number for your ticket tool **Keys** -- `type` - This will be your commit convention and will be your start of your commit - e.g.: `Feat:` +- `type` (`string` or `false`) - This will be your commit convention and will be your start of your commit - e.g.: `Feat:` +- `prefix` (optional) - This option is just valid, if `type` is `false` - `description` (optional) - The description to explain what your type is about - `emoji` (optional) - An emoji which will be appended at the beginning of the commit ([Emoji Cheat Sheet](https://www.webpagefx.com/tools/emoji-cheat-sheet/)) - `argKeys` | Array (optional) - Keys which will be accessed through the `-t` [parameter](#usage-with-parameters)