diff --git a/README.md b/README.md index 8669123..cf3c849 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Suitable for large teams working with multiple projects with their own commit sc ## Steps: -* install commitizen in case you don't have it: `npm install -g commitizen` +* install commitizen in case you don't have it: `npm install -g commitizen`. Make sure you have version `2.8.1`+. * install the cz-customizable: `npm install cz-customizable --save-dev` * configure `commitizen` to use `cz-customizable` as plugin. Add those lines to your `package.json`: diff --git a/buildCommit.js b/buildCommit.js new file mode 100644 index 0000000..5f6c841 --- /dev/null +++ b/buildCommit.js @@ -0,0 +1,62 @@ +'use strict'; + + +var wrap = require('word-wrap'); + + +module.exports = function buildCommit(answers) { + + var maxLineWidth = 100; + + var wrapOptions = { + trim: true, + newline: '\n', + indent:'', + width: maxLineWidth + }; + + function addScope(scope) { + if (!scope) return ': '; //it could be type === WIP. So there is no scope + + return '(' + scope.trim() + '): '; + } + + function addSubject(subject) { + return subject.trim(); + } + + function escapeSpecialChars(result) { + var specialChars = ['\`']; + + specialChars.map(function (item) { + // For some strange reason, we have to pass additional '\' slash to commitizen. Total slashes are 4. + // If user types "feat: `string`", the commit preview should show "feat: `\\string\\`". + // Don't worry. The git log will be "feat: `string`" + result = result.replace(new RegExp(item, 'g'), '\\\\`'); + }); + return result; + } + + // Hard limit this line + var head = (answers.type + addScope(answers.scope) + addSubject(answers.subject)).slice(0, maxLineWidth); + + // Wrap these lines at 100 characters + var body = wrap(answers.body, wrapOptions) || ''; + body = body.split('|').join('\n'); + + var breaking = wrap(answers.breaking, wrapOptions); + var footer = wrap(answers.footer, wrapOptions); + + var result = head; + if (body) { + result += '\n\n' + body; + } + if (breaking) { + result += '\n\n' + 'BREAKING CHANGE:\n' + breaking; + } + if (footer) { + result += '\n\nISSUES CLOSED: ' + footer; + } + + return escapeSpecialChars(result); +}; diff --git a/index.js b/index.js index 80759ae..26d0e49 100644 --- a/index.js +++ b/index.js @@ -4,13 +4,14 @@ var CZ_CONFIG_NAME = '.cz-config.js'; var CZ_CONFIG_EXAMPLE_LOCATION = './cz-config-EXAMPLE.js'; -var wrap = require('word-wrap'); var findConfig = require('find-config'); var log = require('winston'); var editor = require('editor'); var temp = require('temp').track(); var fs = require('fs'); var path = require('path'); +var buildCommit = require('./buildCommit'); + /* istanbul ignore next */ function readConfigFile() { @@ -32,188 +33,25 @@ function readConfigFile() { var config = findConfig.require(CZ_CONFIG_NAME, {home: false}); if (config) { - console.info('>>> Using cz-customizable file ".cz-config.js"'); + console.info('>>> cz-customizable config file has been found.'); return config; } log.warn('Unable to find a configuration file. Please refer to documentation to learn how to ser up: https://github.com/leonardoanalista/cz-customizable#steps "'); - - // if (!config) { - // log.warn('Unable to find a config file "' + CZ_CONFIG_NAME + '". Default configuration would be used.'); - // log.warn('Copy and use it as template by running in a project root directory:\n "cp ' - // + path.resolve(CZ_CONFIG_EXAMPLE_LOCATION) + ' ' + path.join('.', CZ_CONFIG_NAME) + '"'); - - // config = require(CZ_CONFIG_EXAMPLE_LOCATION); - // } } -function buildCommit(answers) { - var maxLineWidth = 100; - - var wrapOptions = { - trim: true, - newline: '\n', - indent:'', - width: maxLineWidth - }; - - function addScope(scope) { - if (!scope) return ': '; //it could be type === WIP. So there is no scope - - return '(' + scope.trim() + '): '; - } - - function addSubject(subject) { - return subject.trim(); - } - - function escapeSpecialChars(result) { - var specialChars = ['\`']; - - specialChars.map(function (item) { - // For some strange reason, we have to pass additional '\' slash to commitizen. Total slashes are 4. - // If user types "feat: `string`", the commit preview should show "feat: `\\string\\`". - // Don't worry. The git log will be "feat: `string`" - result = result.replace(new RegExp(item, 'g'), '\\\\`'); - }); - return result; - } - - // Hard limit this line - var head = (answers.type + addScope(answers.scope) + addSubject(answers.subject)).slice(0, maxLineWidth); - - // Wrap these lines at 100 characters - var body = wrap(answers.body, wrapOptions) || ''; - body = body.split('|').join('\n'); - - var breaking = wrap(answers.breaking, wrapOptions); - var footer = wrap(answers.footer, wrapOptions); - - var result = head; - if (body) { - result += '\n\n' + body; - } - if (breaking) { - result += '\n\n' + 'BREAKING CHANGE:\n' + breaking; - } - if (footer) { - result += '\n\nISSUES CLOSED: ' + footer; - } - - return escapeSpecialChars(result); -} - -var isNotWip = function(answers) { - return answers.type.toLowerCase() !== 'wip'; -}; module.exports = { prompter: function(cz, commit) { var config = readConfigFile(); - config.scopeOverrides = config.scopeOverrides || {}; log.info('\n\nLine 1 will be cropped at 100 characters. All other lines will be wrapped after 100 characters.\n'); - cz.prompt([ - { - type: 'list', - name: 'type', - message: 'Select the type of change that you\'re committing:', - choices: config.types - }, - { - type: 'list', - name: 'scope', - message: '\nDenote the SCOPE of this change (optional):', - choices: function(answers) { - var scopes = []; - if (config.scopeOverrides[answers.type]) { - scopes = scopes.concat(config.scopeOverrides[answers.type]); - } else { - scopes = scopes.concat(config.scopes); - } - if (config.allowCustomScopes || scopes.length === 0) { - scopes = scopes.concat([ - new cz.Separator(), - { name: 'empty', value: false }, - { name: 'custom', value: 'custom' } - ]); - } - return scopes; - }, - when: function(answers) { - var hasScope = false; - if (config.scopeOverrides[answers.type]) { - hasScope = !!(config.scopeOverrides[answers.type].length > 0); - } else { - hasScope = !!(config.scopes && (config.scopes.length > 0)); - } - if (!hasScope) { - answers.scope = 'custom'; - return false; - } else { - return isNotWip(answers); - } - } - }, - { - type: 'input', - name: 'scope', - message: 'Denote the SCOPE of this change:', - when: function(answers) { - return answers.scope === 'custom'; - } - }, - { - type: 'input', - name: 'subject', - message: 'Write a SHORT, IMPERATIVE tense description of the change:\n', - validate: function(value) { - return !!value; - }, - filter: function(value) { - return value.charAt(0).toLowerCase() + value.slice(1); - } - }, - { - type: 'input', - name: 'body', - message: 'Provide a LONGER description of the change (optional). Use "|" to break new line:\n' - }, - { - type: 'input', - name: 'breaking', - message: 'List any BREAKING CHANGES (optional):\n', - when: function(answers) { - if (config.allowBreakingChanges) { - return config.allowBreakingChanges.indexOf(answers.type.toLowerCase()) >= 0; - } + var questions = require('./questions').getQuestions(config, cz); + + cz.prompt(questions).then(function(answers) { - return true; - } - }, - { - type: 'input', - name: 'footer', - message: 'List any ISSUES CLOSED by this change (optional). E.g.: #31, #34:\n', - when: isNotWip - }, - { - type: 'expand', - name: 'confirmCommit', - choices: [ - { key: 'y', name: 'Yes', value: 'yes' }, - { key: 'n', name: 'Abort commit', value: 'no' }, - { key: 'e', name: 'Edit message', value: 'edit' } - ], - message: function(answers) { - var SEP = '###--------------------------------------------------------###'; - log.info('\n' + SEP + '\n' + buildCommit(answers) + '\n' + SEP + '\n'); - return 'Are you sure you want to proceed with the commit above?'; - } - } - ], function(answers) { if (answers.confirmCommit === 'edit') { temp.open(null, function(err, info) { /* istanbul ignore else */ diff --git a/package.json b/package.json index ecf07d2..478f782 100644 --- a/package.json +++ b/package.json @@ -23,15 +23,15 @@ ], "license": "MIT", "dependencies": { - "editor": "^1.0.0", - "find-config": "^0.3.0", - "temp": "^0.8.3", + "editor": "1.0.0", + "find-config": "0.3.0", + "temp": "0.8.3", "winston": "2.1.0", "word-wrap": "1.1.0" }, "devDependencies": { "codecov.io": "0.1.6", - "commitizen": "^2.4.6", + "commitizen": "2.8.1", "eslint": "1.9.0", "ghooks": "1.0.0", "istanbul": "0.4.0", @@ -41,7 +41,10 @@ }, "config": { "commitizen": { - "path": "." + "path": "./index.js" + }, + "cz-customizable": { + "config": "cz-config-EXAMPLE.js" }, "ghooks": { "pre-commit": "npm run eslint && npm run test" diff --git a/questions.js b/questions.js new file mode 100644 index 0000000..2b72168 --- /dev/null +++ b/questions.js @@ -0,0 +1,120 @@ +'use strict'; + + +var buildCommit = require('./buildCommit'); +var log = require('winston'); + + +var isNotWip = function(answers) { + return answers.type.toLowerCase() !== 'wip'; +}; + +module.exports = { + + getQuestions: function(config, cz) { + + // normalize config optional options + config.scopeOverrides = config.scopeOverrides || {}; + + var questions = [ + { + type: 'list', + name: 'type', + message: 'Select the type of change that you\'re committing:', + choices: config.types + }, + { + type: 'list', + name: 'scope', + message: '\nDenote the SCOPE of this change (optional):', + choices: function(answers) { + var scopes = []; + if (config.scopeOverrides[answers.type]) { + scopes = scopes.concat(config.scopeOverrides[answers.type]); + } else { + scopes = scopes.concat(config.scopes); + } + if (config.allowCustomScopes || scopes.length === 0) { + scopes = scopes.concat([ + new cz.Separator(), + { name: 'empty', value: false }, + { name: 'custom', value: 'custom' } + ]); + } + return scopes; + }, + when: function(answers) { + var hasScope = false; + if (config.scopeOverrides[answers.type]) { + hasScope = !!(config.scopeOverrides[answers.type].length > 0); + } else { + hasScope = !!(config.scopes && (config.scopes.length > 0)); + } + if (!hasScope) { + answers.scope = 'custom'; + return false; + } else { + return isNotWip(answers); + } + } + }, + { + type: 'input', + name: 'scope', + message: 'Denote the SCOPE of this change:', + when: function(answers) { + return answers.scope === 'custom'; + } + }, + { + type: 'input', + name: 'subject', + message: 'Write a SHORT, IMPERATIVE tense description of the change:\n', + validate: function(value) { + return !!value; + }, + filter: function(value) { + return value.charAt(0).toLowerCase() + value.slice(1); + } + }, + { + type: 'input', + name: 'body', + message: 'Provide a LONGER description of the change (optional). Use "|" to break new line:\n' + }, + { + type: 'input', + name: 'breaking', + message: 'List any BREAKING CHANGES (optional):\n', + when: function(answers) { + if (config.allowBreakingChanges && config.allowBreakingChanges.indexOf(answers.type.toLowerCase()) >= 0) { + return true; + } + return false; // no breaking changes allowed unless specifed + } + }, + { + type: 'input', + name: 'footer', + message: 'List any ISSUES CLOSED by this change (optional). E.g.: #31, #34:\n', + when: isNotWip + }, + { + type: 'expand', + name: 'confirmCommit', + choices: [ + { key: 'y', name: 'Yes', value: 'yes' }, + { key: 'n', name: 'Abort commit', value: 'no' }, + { key: 'e', name: 'Edit message', value: 'edit' } + ], + message: function(answers) { + var SEP = '###--------------------------------------------------------###'; + log.info('\n' + SEP + '\n' + buildCommit(answers) + '\n' + SEP + '\n'); + return 'Are you sure you want to proceed with the commit above?'; + } + } + ]; + + return questions; + } +}; diff --git a/spec/czCustomizableSpec.js b/spec/czCustomizableSpec.js index a7b7ca9..f28fb97 100644 --- a/spec/czCustomizableSpec.js +++ b/spec/czCustomizableSpec.js @@ -32,96 +32,56 @@ describe('cz-customizable', function() { commit = jasmine.createSpy(); }); - it('should escape special characters sush as backticks', function() { - module.prompter(cz, commit); - var commitAnswers = cz.prompt.mostRecentCall.args[1]; + function getMockedCz(answers) { + return { + prompt: function() { + return { + then: function (cb) { + cb(answers); + } + }; + } + }; + } + it('should commit without confirmation', function() { var answers = { confirmCommit: 'yes', type: 'feat', - subject: 'with backticks `here`' + subject: 'do it all' }; - commitAnswers(answers); - expect(commit).toHaveBeenCalledWith('feat: with backticks \\\\`here\\\\`'); - }); - - it('should call cz.prompt with questions', function() { - module.prompter(cz, commit); + var mockCz = getMockedCz(answers); - var getQuestion = function(number) { - return cz.prompt.mostRecentCall.args[0][number - 1]; - }; - - //question 1 - expect(getQuestion(1).name).toEqual('type'); - expect(getQuestion(1).type).toEqual('list'); - expect(getQuestion(1).choices[0]).toEqual({value: 'feat', name: 'feat: my feat'}); - - //question 2 - expect(getQuestion(2).name).toEqual('scope'); - expect(getQuestion(2).choices({})[0]).toEqual({name: 'myScope'}); - expect(getQuestion(2).choices({type: 'fix'})[0]).toEqual({name: 'fixOverride'}); //should override scope - expect(getQuestion(2).when({type: 'fix'})).toEqual(true); - expect(getQuestion(2).when({type: 'WIP'})).toEqual(false); - expect(getQuestion(2).when({type: 'wip'})).toEqual(false); - - //question 3 - expect(getQuestion(3).name).toEqual('scope'); - expect(getQuestion(3).when({scope: 'custom'})).toEqual(true); - expect(getQuestion(3).when({scope: false})).toEqual(false); - expect(getQuestion(3).when({scope: 'scope'})).toEqual(false); - - //question 4 - expect(getQuestion(4).name).toEqual('subject'); - expect(getQuestion(4).type).toEqual('input'); - expect(getQuestion(4).message).toMatch(/IMPERATIVE tense description/); - expect(getQuestion(4).validate()).toEqual(false); //mandatory question - expect(getQuestion(4).filter('Subject')).toEqual('subject'); - - //question 5 - expect(getQuestion(5).name).toEqual('body'); - expect(getQuestion(5).type).toEqual('input'); - - //question 6 - expect(getQuestion(6).name).toEqual('breaking'); - expect(getQuestion(6).type).toEqual('input'); - expect(getQuestion(6).when({type: 'feat'})).toEqual(true); - expect(getQuestion(6).when({type: 'fix'})).toEqual(false); - - //question 7 - expect(getQuestion(7).name).toEqual('footer'); - expect(getQuestion(7).type).toEqual('input'); - expect(getQuestion(7).when({type: 'fix'})).toEqual(true); - expect(getQuestion(7).when({type: 'WIP'})).toEqual(false); - - //question 8, last one - expect(getQuestion(8).name).toEqual('confirmCommit'); - expect(getQuestion(8).type).toEqual('expand'); + // run commitizen plugin + module.prompter(mockCz, commit); + expect(commit).toHaveBeenCalledWith('feat: do it all'); + }); + it('should escape special characters sush as backticks', function() { var answers = { confirmCommit: 'yes', type: 'feat', - scope: 'myScope', - subject: 'create a new cool feature' + subject: 'with backticks `here`' }; - expect(getQuestion(8).message(answers)).toMatch('Are you sure you want to proceed with the commit above?'); + + var mockCz = getMockedCz(answers); + module.prompter(mockCz, commit); + + expect(commit).toHaveBeenCalledWith('feat: with backticks \\\\`here\\\\`'); }); - it('should not call commit() function if there is no final confirmation', function() { - module.prompter(cz, commit); - var commitAnswers = cz.prompt.mostRecentCall.args[1]; - var res = commitAnswers({}); + it('should not call commit() function if there is no final confirmation and display log message saying commit has been canceled', function() { + var mockCz = getMockedCz({}); + + // run commitizen plugin + module.prompter(mockCz, commit); - expect(res).toEqual(undefined); expect(commit).not.toHaveBeenCalled(); }); it('should call commit() function with commit message when user confirms commit and split body when pipes are present', function() { - module.prompter(cz, commit); - var commitAnswers = cz.prompt.mostRecentCall.args[1]; - var answers = { confirmCommit: 'yes', type: 'feat', @@ -132,14 +92,13 @@ describe('cz-customizable', function() { footer: 'my footer' }; - commitAnswers(answers); + var mockCz = getMockedCz(answers); + module.prompter(mockCz, commit); + expect(commit).toHaveBeenCalledWith('feat(myScope): create a new cool feature\n\n-line1\n-line2\n\nBREAKING CHANGE:\nbreaking\n\nISSUES CLOSED: my footer'); }); it('should call commit() function with commit message with the minimal required fields', function() { - module.prompter(cz, commit); - var commitAnswers = cz.prompt.mostRecentCall.args[1]; - var answers = { confirmCommit: 'yes', type: 'feat', @@ -147,27 +106,24 @@ describe('cz-customizable', function() { subject: 'create a new cool feature' }; - commitAnswers(answers); + var mockCz = getMockedCz(answers); + module.prompter(mockCz, commit); expect(commit).toHaveBeenCalledWith('feat(myScope): create a new cool feature'); }); it('should suppress scope when commit type is WIP', function() { - module.prompter(cz, commit); - var commitAnswers = cz.prompt.mostRecentCall.args[1]; - var answers = { confirmCommit: 'yes', type: 'WIP', subject: 'this is my work-in-progress' }; - commitAnswers(answers); + var mockCz = getMockedCz(answers); + module.prompter(mockCz, commit); expect(commit).toHaveBeenCalledWith('WIP: this is my work-in-progress'); }); it('should allow edit message before commit', function(done) { - module.prompter(cz, commit); - var commitAnswers = cz.prompt.mostRecentCall.args[1]; process.env.EDITOR = 'true'; var answers = { @@ -176,7 +132,9 @@ describe('cz-customizable', function() { subject: 'create a new cool feature' }; - commitAnswers(answers); + var mockCz = getMockedCz(answers); + module.prompter(mockCz, commit); + setTimeout(function() { expect(commit).toHaveBeenCalledWith('feat: create a new cool feature'); done(); @@ -184,8 +142,6 @@ describe('cz-customizable', function() { }); it('should not commit if editor returned non-zero value', function(done) { - module.prompter(cz, commit); - var commitAnswers = cz.prompt.mostRecentCall.args[1]; process.env.EDITOR = 'false'; var answers = { @@ -194,7 +150,9 @@ describe('cz-customizable', function() { subject: 'create a new cool feature' }; - commitAnswers(answers); + var mockCz = getMockedCz(answers); + module.prompter(mockCz, commit); + setTimeout(function() { expect(commit.wasCalled).toEqual(false); done(); @@ -202,9 +160,6 @@ describe('cz-customizable', function() { }); it('should truncate first line if number of characters is higher than 200', function() { - module.prompter(cz, commit); - var commitAnswers = cz.prompt.mostRecentCall.args[1]; - var chars_100 = '0123456789-0123456789-0123456789-0123456789-0123456789-0123456789-0123456789-0123456789-0123456789-0123456789'; // this string will be prepend: "ISSUES CLOSED: " = 15 chars @@ -219,7 +174,8 @@ describe('cz-customizable', function() { footer: footerChars_100 + ' footer-second-line' }; - commitAnswers(answers); + var mockCz = getMockedCz(answers); + module.prompter(mockCz, commit); var firstPart = 'feat(myScope): '; @@ -236,99 +192,4 @@ describe('cz-customizable', function() { }); - - describe('optional fixOverride and allowBreakingChanges', function() { - - beforeEach(function() { - module.__set__({ - readConfigFile: function() { - return { - types: [{value: 'feat', name: 'feat: my feat'}], - scopes: [{name: 'myScope'}] - }; - } - }); - - cz = jasmine.createSpyObj('cz', ['prompt', 'Separator']); - commit = jasmine.createSpy(); - }); - - - it('should call cz.prompt with questions', function() { - module.prompter(cz, commit); - - var getQuestion = function(number) { - return cz.prompt.mostRecentCall.args[0][number - 1]; - }; - - //question 1 - expect(getQuestion(1).name).toEqual('type'); - expect(getQuestion(1).type).toEqual('list'); - expect(getQuestion(1).choices[0]).toEqual({value: 'feat', name: 'feat: my feat'}); - - //question 2 - expect(getQuestion(2).name).toEqual('scope'); - expect(getQuestion(2).choices({})[0]).toEqual({name: 'myScope'}); - expect(getQuestion(2).choices({type: 'fix'})[0]).toEqual({name: 'myScope'}); //should override scope - expect(getQuestion(2).when({type: 'fix'})).toEqual(true); - expect(getQuestion(2).when({type: 'WIP'})).toEqual(false); - expect(getQuestion(2).when({type: 'wip'})).toEqual(false); - - //question 6 - expect(getQuestion(6).name).toEqual('breaking'); - expect(getQuestion(6).when({type: 'feat'})).toEqual(true); - expect(getQuestion(6).when({type: 'fix'})).toEqual(true); - expect(getQuestion(6).when({type: 'FIX'})).toEqual(true); - - var answers = { - confirmCommit: 'yes', - type: 'feat', - scope: 'myScope', - subject: 'create a new cool feature' - }; - expect(getQuestion(8).message(answers)).toMatch('Are you sure you want to proceed with the commit above?'); - }); - }); - - describe('optional scopes', function() { - - beforeEach(function() { - module.__set__({ - readConfigFile: function() { - return { - types: [{value: 'feat', name: 'feat: my feat'}], - scopeOverrides: { - feat: [{name: 'myScope'}] - } - }; - } - }); - - cz = jasmine.createSpyObj('cz', ['prompt', 'Separator']); - commit = jasmine.createSpy(); - }); - - - it('should call cz.prompt with questions', function() { - module.prompter(cz, commit); - - var getQuestion = function(number) { - return cz.prompt.mostRecentCall.args[0][number - 1]; - }; - - //question 2 - expect(getQuestion(2).name).toEqual('scope'); - expect(getQuestion(2).choices({})[0]).toBeUndefined(); - expect(getQuestion(2).choices({type: 'feat'})[0]).toEqual({name: 'myScope'}); //should override scope - expect(getQuestion(2).when({type: 'feat'})).toEqual(true); - (function () { - var answers = {type: 'fix'}; - expect(getQuestion(2).when(answers)).toEqual(false); - expect(answers.scope).toEqual('custom'); - })(); - - }); - }); - - }); diff --git a/spec/questionsSpec.js b/spec/questionsSpec.js new file mode 100644 index 0000000..0821c23 --- /dev/null +++ b/spec/questionsSpec.js @@ -0,0 +1,147 @@ +'use strict'; + +describe('cz-customizable', function() { + + var questions, config; + + beforeEach(function() { + questions = require('../questions.js'); + config = null; + }); + + var mockedCz = { + Separator: jasmine.createSpy() + }; + + var getQuestion = function(number) { + return questions.getQuestions(config, mockedCz)[number - 1]; + }; + + it('should array of questions be returned', function() { + config = { + types: [{value: 'feat', name: 'feat: my feat'}], + scopes: [{name: 'myScope'}], + scopeOverrides: { + fix: [{name: 'fixOverride'}] + }, + allowCustomScopes: true, + allowBreakingChanges: ['feat'] + }; + + // question 1 - TYPE + expect(getQuestion(1).name).toEqual('type'); + expect(getQuestion(1).type).toEqual('list'); + expect(getQuestion(1).choices[0]).toEqual({value: 'feat', name: 'feat: my feat'}); + + // question 2 - SCOPE + expect(getQuestion(2).name).toEqual('scope'); + expect(getQuestion(2).choices({})[0]).toEqual({name: 'myScope'}); + expect(getQuestion(2).choices({type: 'fix'})[0]).toEqual({name: 'fixOverride'}); //should override scope + expect(getQuestion(2).when({type: 'fix'})).toEqual(true); + expect(getQuestion(2).when({type: 'WIP'})).toEqual(false); + expect(getQuestion(2).when({type: 'wip'})).toEqual(false); + + // question 3 - SCOPE CUSTOM + expect(getQuestion(3).name).toEqual('scope'); + expect(getQuestion(3).when({scope: 'custom'})).toEqual(true); + expect(getQuestion(3).when({scope: false})).toEqual(false); + expect(getQuestion(3).when({scope: 'scope'})).toEqual(false); + + // question 4 - SUBJECT + expect(getQuestion(4).name).toEqual('subject'); + expect(getQuestion(4).type).toEqual('input'); + expect(getQuestion(4).message).toMatch(/IMPERATIVE tense description/); + expect(getQuestion(4).validate()).toEqual(false); //mandatory question + expect(getQuestion(4).filter('Subject')).toEqual('subject'); + + // question 5 - BODY + expect(getQuestion(5).name).toEqual('body'); + expect(getQuestion(5).type).toEqual('input'); + + // question 6 - BREAKING CHANGE + expect(getQuestion(6).name).toEqual('breaking'); + expect(getQuestion(6).type).toEqual('input'); + expect(getQuestion(6).when({type: 'feat'})).toEqual(true); + expect(getQuestion(6).when({type: 'fix'})).toEqual(false); + + // question 7 - FOOTER + expect(getQuestion(7).name).toEqual('footer'); + expect(getQuestion(7).type).toEqual('input'); + expect(getQuestion(7).when({type: 'fix'})).toEqual(true); + expect(getQuestion(7).when({type: 'WIP'})).toEqual(false); + + //question 8, last one, CONFIRM COMMIT OR NOT + expect(getQuestion(8).name).toEqual('confirmCommit'); + expect(getQuestion(8).type).toEqual('expand'); + + + var answers = { + confirmCommit: 'yes', + type: 'feat', + scope: 'myScope', + subject: 'create a new cool feature' + }; + expect(getQuestion(8).message(answers)).toMatch('Are you sure you want to proceed with the commit above?'); + }); + + + describe('optional fixOverride and allowBreakingChanges', function() { + + it('should restrict BREAKING CHANGE question when config property "allowBreakingChanges" specifies array of types', function() { + config = { + types: [{value: 'feat', name: 'feat: my feat'}], + scopes: [{name: 'myScope'}], + allowBreakingChanges: ['fix'] + }; + expect(getQuestion(6).name).toEqual('breaking'); + + var answers = { + type: 'feat' + }; + + expect(getQuestion(6).when(answers)).toEqual(false); // not allowed + }); + + it('should allow BREAKING CHANGE question when config property "allowBreakingChanges" specifies array of types and answer is one of those', function() { + config = { + types: [{value: 'feat', name: 'feat: my feat'}], + scopes: [{name: 'myScope'}], + allowBreakingChanges: ['fix', 'feat'] + }; + expect(getQuestion(6).name).toEqual('breaking'); + + var answers = { + type: 'feat' + }; + + expect(getQuestion(6).when(answers)).toEqual(true); // allowed + }); + + }); + + describe('Optional scopes', function() { + + it('should use scope override', function() { + config = { + types: [{value: 'feat', name: 'feat: my feat'}], + scopeOverrides: { + feat: [{name: 'myScope'}] + } + }; + + // question 2 with + expect(getQuestion(2).name).toEqual('scope'); + expect(getQuestion(2).choices({})[0]).toBeUndefined(); + expect(getQuestion(2).choices({type: 'feat'})[0]).toEqual({name: 'myScope'}); //should override scope + expect(getQuestion(2).when({type: 'feat'})).toEqual(true); + (function () { + var answers = {type: 'fix'}; + expect(getQuestion(2).when(answers)).toEqual(false); + expect(answers.scope).toEqual('custom'); + })(); + + }); + }); + + +});