UNPKG

charlike

Version:

Small, fast, simple and streaming project scaffolder for myself, but not only. Supports hundreds of template engines through the @JSTransformers API or if you want custom `render` function passed through options

859 lines (776 loc) 27.1 kB
'use strict'; var expect = require('chai').expect; var parser = require('../lib/parser'); var regex = require('../lib/regex'); describe('parser', function() { var options; var reg; var msg; var simpleMsg; var longNoteMsg; var headerOnlyMsg; beforeEach(function() { options = { revertPattern: /^Revert\s"([\s\S]*)"\s*This reverts commit (.*)\.$/, revertCorrespondence: ['header', 'hash'], fieldPattern: /^-(.*?)-$/, headerPattern: /^(\w*)(?:\(([\w\$\.\-\* ]*)\))?\: (.*)$/, headerCorrespondence: ['type', 'scope', 'subject'], noteKeywords: ['BREAKING AMEND'], issuePrefixes: ['#', 'gh-'], referenceActions: [ 'kill', 'kills', 'killed', 'handle', 'handles', 'handled' ] }; reg = regex(options); msg = parser( 'feat(scope): broadcast $destroy event on scope destruction\n' + 'perf testing shows that in chrome this change adds 5-15% overhead\n' + 'when destroying 10k nested scopes where each scope has a $destroy listener\n' + 'BREAKING AMEND: some breaking change\n' + 'Kills #1, #123\n' + 'killed #25\n' + 'handle #33, Closes #100, Handled #3 kills repo#77\n' + 'kills stevemao/conventional-commits-parser#1', options, reg ); longNoteMsg = parser( 'feat(scope): broadcast $destroy event on scope destruction\n' + 'perf testing shows that in chrome this change adds 5-15% overhead\n' + 'when destroying 10k nested scopes where each scope has a $destroy listener\n' + 'BREAKING AMEND:\n' + 'some breaking change\n' + 'some other breaking change\n' + 'Kills #1, #123\n' + 'killed #25\n' + 'handle #33, Closes #100, Handled #3', options, reg ); simpleMsg = parser( 'chore: some chore\n', options, reg ); headerOnlyMsg = parser('header', options, reg); }); it('should throw if nothing to parse', function() { expect(function() { parser(); }).to.throw('Expected a raw commit'); expect(function() { parser('\n'); }).to.throw('Expected a raw commit'); expect(function() { parser(' '); }).to.throw('Expected a raw commit'); }); it('should throw if `options` is empty', function() { expect(function() { parser('bla bla'); }).to.throw('Expected options'); }); it('should throw if `regex` is empty', function() { expect(function() { parser('bla bla', { headerPattern: /^(\w*)(?:\(([\w\$\.\-\* ]*)\))?\: (.*)$/ }); }).to.throw('Expected regex'); }); it('should trim extra newlines', function() { expect(parser( '\n\n\n\n\n\n\nfeat(scope): broadcast $destroy event on scope destruction\n\n\n' + '\n\n\nperf testing shows that in chrome this change adds 5-15% overhead\n' + '\n\n\nwhen destroying 10k nested scopes where each scope has a $destroy listener\n\n' + '\n\n\n\nBREAKING AMEND: some breaking change\n' + '\n\n\n\nBREAKING AMEND: An awesome breaking change\n\n\n```\ncode here\n```' + '\n\nKills #1\n' + '\n\n\nkilled #25\n\n\n\n\n', options, reg )).to.eql({ merge: null, header: 'feat(scope): broadcast $destroy event on scope destruction', body: 'perf testing shows that in chrome this change adds 5-15% overhead\n\n\n\nwhen destroying 10k nested scopes where each scope has a $destroy listener', footer: 'BREAKING AMEND: some breaking change\n\n\n\n\nBREAKING AMEND: An awesome breaking change\n\n\n```\ncode here\n```\n\nKills #1\n\n\n\nkilled #25', notes: [{ title: 'BREAKING AMEND', text: 'some breaking change' }, { title: 'BREAKING AMEND', text: 'An awesome breaking change\n\n\n```\ncode here\n```' }], references: [{ action: 'Kills', owner: null, repository: null, issue: '1', raw: '#1', prefix: '#' }, { action: 'killed', owner: null, repository: null, issue: '25', raw: '#25', prefix: '#' }], mentions: [], revert: null, scope: 'scope', subject: 'broadcast $destroy event on scope destruction', type: 'feat' }); }); it('should keep spaces', function() { expect(parser( ' feat(scope): broadcast $destroy event on scope destruction \n' + ' perf testing shows that in chrome this change adds 5-15% overhead \n\n' + ' when destroying 10k nested scopes where each scope has a $destroy listener \n' + ' BREAKING AMEND: some breaking change \n\n' + ' BREAKING AMEND: An awesome breaking change\n\n\n```\ncode here\n```' + '\n\n Kills #1\n', options, reg )).to.eql({ merge: null, header: ' feat(scope): broadcast $destroy event on scope destruction ', body: ' perf testing shows that in chrome this change adds 5-15% overhead \n\n when destroying 10k nested scopes where each scope has a $destroy listener ', footer: ' BREAKING AMEND: some breaking change \n\n BREAKING AMEND: An awesome breaking change\n\n\n```\ncode here\n```\n\n Kills #1', notes: [{ title: 'BREAKING AMEND', text: 'some breaking change ' }, { title: 'BREAKING AMEND', text: 'An awesome breaking change\n\n\n```\ncode here\n```' }], references: [{ action: 'Kills', owner: null, repository: null, issue: '1', raw: '#1', prefix: '#' }], mentions: [], revert: null, scope: null, subject: null, type: null }); }); describe('mentions', function() { it('should mention someone in the commit', function() { var options = { headerPattern: /^(\w*)(?:\(([\w\$\.\-\* ]*)\))?\: (.*)$/, headerCorrespondence: ['type', 'scope', 'subject'], mergePattern: /^Merge pull request #(\d+) from (.*)$/, mergeCorrespondence: ['issueId', 'source'] }; var reg = regex(options); var msg = parser( '@Steve\n' + '@conventional-changelog @someone' + '\n' + 'perf testing shows that in chrome this change adds 5-15% overhead\n' + '@this is', options, reg ); expect(msg.mentions).to.eql([ 'Steve', 'conventional-changelog', 'someone', 'this' ]); }); }); describe('merge commits', function() { var mergeOptions = { headerPattern: /^(\w*)(?:\(([\w\$\.\-\* ]*)\))?\: (.*)$/, headerCorrespondence: ['type', 'scope', 'subject'], mergePattern: /^Merge branch \'(\w+)\'$/, mergeCorrespondence: ['source', 'issueId'] }; var mergeRegex = regex(mergeOptions); var mergeMsg = parser( 'Merge branch \'feature\'\nHEADER', mergeOptions, mergeRegex ); it('should parse merge header in merge commit', function() { expect(mergeMsg.source).to.equal('feature'); expect(mergeMsg.issueId).to.equal(null); }); var githubOptions = { headerPattern: /^(\w*)(?:\(([\w\$\.\-\* ]*)\))?\: (.*)$/, headerCorrespondence: ['type', 'scope', 'subject'], mergePattern: /^Merge pull request #(\d+) from (.*)$/, mergeCorrespondence: ['issueId', 'source'] }; var githubRegex = regex(githubOptions); var githubMsg = parser( 'Merge pull request #1 from user/feature/feature-name\n' + '\n' + 'feat(scope): broadcast $destroy event on scope destruction\n' + '\n' + 'perf testing shows that in chrome this change adds 5-15% overhead\n' + 'when destroying 10k nested scopes where each scope has a $destroy listener', githubOptions, githubRegex ); it('should parse header in GitHub like pull request', function() { expect(githubMsg.header).to.equal('feat(scope): broadcast $destroy event on scope destruction'); }); it('should understand header parts in GitHub like pull request', function() { expect(githubMsg.type).to.equal('feat'); expect(githubMsg.scope).to.equal('scope'); expect(githubMsg.subject).to.equal('broadcast $destroy event on scope destruction'); }); it('should understand merge parts in GitHub like pull request', function() { expect(githubMsg.merge).to.equal('Merge pull request #1 from user/feature/feature-name'); expect(githubMsg.issueId).to.equal('1'); expect(githubMsg.source).to.equal('user/feature/feature-name'); }); var gitLabOptions = { headerPattern: /^(\w*)(?:\(([\w\$\.\-\* ]*)\))?\: (.*)$/, headerCorrespondence: ['type', 'scope', 'subject'], mergePattern: /^Merge branch '([^']+)' into '[^']+'$/, mergeCorrespondence: ['source'] }; var gitLabRegex = regex(gitLabOptions); var gitlabMsg = parser( 'Merge branch \'feature/feature-name\' into \'master\'\r\n' + '\r\n' + 'feat(scope): broadcast $destroy event on scope destruction\r\n' + '\r\n' + 'perf testing shows that in chrome this change adds 5-15% overhead\r\n' + 'when destroying 10k nested scopes where each scope has a $destroy listener\r\n' + '\r\n' + 'See merge request !1', gitLabOptions, gitLabRegex ); it('should parse header in GitLab like merge request', function() { expect(gitlabMsg.header).to.equal('feat(scope): broadcast $destroy event on scope destruction'); }); it('should understand header parts in GitLab like merge request', function() { expect(gitlabMsg.type).to.equal('feat'); expect(gitlabMsg.scope).to.equal('scope'); expect(gitlabMsg.subject).to.equal('broadcast $destroy event on scope destruction'); }); it('should understand merge parts in GitLab like merge request', function() { expect(gitlabMsg.merge).to.equal('Merge branch \'feature/feature-name\' into \'master\''); expect(gitlabMsg.source).to.equal('feature/feature-name'); }); it('Should parse header if merge header is missing', function() { var msgWithoutmergeHeader = parser( 'feat(scope): broadcast $destroy event on scope destruction', githubOptions, githubRegex ); expect(msgWithoutmergeHeader.merge).to.equal(null); }); it('merge should be null if options.mergePattern is not defined', function() { expect(msg.merge).to.equal(null); }); it('Should not parse conventional header if pull request header present and mergePattern is not set', function() { var msgWithmergeHeaderWithoutmergePattern = parser( 'Merge pull request #1 from user/feature/feature-name\n' + 'feat(scope): broadcast $destroy event on scope destruction', options, reg ); expect(msgWithmergeHeaderWithoutmergePattern.type).to.equal(null); expect(msgWithmergeHeaderWithoutmergePattern.scope).to.equal(null); expect(msgWithmergeHeaderWithoutmergePattern.subject).to.equal(null); }); }); describe('header', function() { it('should allow ":" in scope', function() { var msg = parser('feat(ng:list): Allow custom separator', { headerPattern: /^(\w*)(?:\(([:\w\$\.\-\* ]*)\))?\: (.*)$/, headerCorrespondence: ['type', 'scope', 'subject'] }, reg); expect(msg.scope).to.equal('ng:list'); }); it('header part should be null if not captured', function() { expect(headerOnlyMsg.type).to.equal(null); expect(headerOnlyMsg.scope).to.equal(null); expect(headerOnlyMsg.subject).to.equal(null); }); it('should parse header', function() { expect(msg.header).to.equal('feat(scope): broadcast $destroy event on scope destruction'); }); it('should understand header parts', function() { expect(msg.type).to.equal('feat'); expect(msg.scope).to.equal('scope'); expect(msg.subject).to.equal('broadcast $destroy event on scope destruction'); }); it('should allow correspondence to be changed', function() { var msg = parser('scope(my subject): fix this', { headerPattern: /^(\w*)(?:\(([\w\$\.\-\* ]*)\))?\: (.*)$/, headerCorrespondence: ['scope', 'subject', 'type'] }, reg); expect(msg.type).to.equal('fix this'); expect(msg.scope).to.equal('scope'); expect(msg.subject).to.equal('my subject'); }); it('should be `undefined` if it is missing in `options.headerCorrespondence`', function() { msg = parser('scope(my subject): fix this', { headerPattern: /^(\w*)(?:\(([\w\$\.\-\* ]*)\))?\: (.*)$/, headerCorrespondence: ['scop', 'subject'] }, reg); expect(msg.scope).to.equal(undefined); }); it('should reference an issue with an owner', function() { var msg = parser('handled angular/angular.js#1', options, reg); expect(msg.references).to.eql([{ action: 'handled', owner: 'angular', repository: 'angular.js', issue: '1', raw: 'angular/angular.js#1', prefix: '#' }]); }); it('should reference an issue with a repository', function() { var msg = parser('handled angular.js#1', options, reg); expect(msg.references).to.eql([{ action: 'handled', owner: null, repository: 'angular.js', issue: '1', raw: 'angular.js#1', prefix: '#' }]); }); it('should reference an issue without both', function() { var msg = parser('handled gh-1', options, reg); expect(msg.references).to.eql([{ action: 'handled', owner: null, repository: null, issue: '1', raw: 'gh-1', prefix: 'gh-' }]); }); it('should reference an issue without an action', function() { var options = { revertPattern: /^Revert\s"([\s\S]*)"\s*This reverts commit (.*)\.$/, revertCorrespondence: ['header', 'hash'], fieldPattern: /^-(.*?)-$/, headerPattern: /^(\w*)(?:\(([\w\$\.\-\* ]*)\))?\: (.*)$/, headerCorrespondence: ['type', 'scope', 'subject'], noteKeywords: ['BREAKING AMEND'], issuePrefixes: ['#', 'gh-'] }; var reg = regex(options); var msg = parser('This is gh-1', options, reg); expect(msg.references).to.eql([{ action: null, owner: null, repository: null, issue: '1', raw: 'This is gh-1', prefix: 'gh-' }]); }); }); describe('body', function() { it('should parse body', function() { expect(msg.body).to.equal( 'perf testing shows that in chrome this change adds 5-15% overhead\n' + 'when destroying 10k nested scopes where each scope has a $destroy listener'); }); it('should be null if not found', function() { expect(headerOnlyMsg.body).to.equal(null); }); }); describe('footer', function() { it('should be null if not found', function() { expect(headerOnlyMsg.footer).to.equal(null); }); it('should parse footer', function() { expect(msg.footer).to.equal( 'BREAKING AMEND: some breaking change\n' + 'Kills #1, #123\n' + 'killed #25\n' + 'handle #33, Closes #100, Handled #3 kills repo#77\n' + 'kills stevemao/conventional-commits-parser#1' ); }); it('important notes should be an empty string if not found', function() { expect(simpleMsg.notes).to.eql([]); }); it('should parse important notes', function() { expect(msg.notes[0]).to.eql({ title: 'BREAKING AMEND', text: 'some breaking change' }); }); it('should parse important notes with more than one paragraphs', function() { expect(longNoteMsg.notes[0]).to.eql({ title: 'BREAKING AMEND', text: 'some breaking change\nsome other breaking change' }); }); it('should parse important notes that start with asterisks (for squash commits)', function() { var text = 'Previously multiple template bindings on one element\n' + '(ex. `<div *ngIf=\'..\' *ngFor=\'...\'>`) were allowed but most of the time\n' + 'were leading to undesired result. It is possible that a small number\n' + 'of applications will see template parse errors that shuld be fixed by\n' + 'nesting elements or using `<template>` tags explicitly.\n' + '\n' + 'Closes #9462'; options.noteKeywords = ['BREAKING CHANGE']; reg = regex(options); var msg = parser( 'fix(core): report duplicate template bindings in templates\n' + '\n' + 'Fixes #7315\n' + '\n' + '* BREAKING CHANGE:\n' + '\n' + text, options, reg ); var expected = { title: 'BREAKING CHANGE', text: text }; expect(msg.notes[0]).to.eql(expected); }); it('should not treat it as important notes if there are texts after `noteKeywords`', function() { options.noteKeywords = ['BREAKING CHANGE']; reg = regex(options); var msg = parser( 'fix(core): report duplicate template bindings in templates\n' + '\n' + 'Fixes #7315\n' + '\n' + 'BREAKING CHANGES:\n' + '\n' + 'Previously multiple template bindings on one element\n' + '(ex. `<div *ngIf=\'..\' *ngFor=\'...\'>`) were allowed but most of the time\n' + 'were leading to undesired result. It is possible that a small number\n' + 'of applications will see template parse errors that shuld be fixed by\n' + 'nesting elements or using `<template>` tags explicitly.\n' + '\n' + 'Closes #9462', options, reg ); expect(msg.notes).to.eql([]); }); it('references should be empty if not found', function() { expect(simpleMsg.references).to.eql([]); }); it('should parse references', function() { expect(msg.references).to.eql([{ action: 'Kills', owner: null, repository: null, issue: '1', raw: '#1', prefix: '#' }, { action: 'Kills', owner: null, repository: null, issue: '123', raw: ', #123', prefix: '#' }, { action: 'killed', owner: null, repository: null, issue: '25', raw: '#25', prefix: '#' }, { action: 'handle', owner: null, repository: null, issue: '33', raw: '#33', prefix: '#' }, { action: 'handle', owner: null, repository: null, issue: '100', raw: ', Closes #100', prefix: '#' }, { action: 'Handled', owner: null, repository: null, issue: '3', raw: '#3', prefix: '#' }, { action: 'kills', owner: null, repository: 'repo', issue: '77', raw: 'repo#77', prefix: '#' }, { action: 'kills', owner: 'stevemao', repository: 'conventional-commits-parser', issue: '1', raw: 'stevemao/conventional-commits-parser#1', prefix: '#' }]); }); it('should reference an issue without an action', function() { var options = { revertPattern: /^Revert\s"([\s\S]*)"\s*This reverts commit (.*)\.$/, revertCorrespondence: ['header', 'hash'], fieldPattern: /^-(.*?)-$/, headerPattern: /^(\w*)(?:\(([\w\$\.\-\* ]*)\))?\: (.*)$/, headerCorrespondence: ['type', 'scope', 'subject'], noteKeywords: ['BREAKING AMEND'], issuePrefixes: ['#', 'gh-'] }; var reg = regex(options); var msg = parser( 'feat(scope): broadcast $destroy event on scope destruction\n' + 'perf testing shows that in chrome this change adds 5-15% overhead\n' + 'when destroying 10k nested scopes where each scope has a $destroy listener\n' + 'Kills #1, gh-123\n' + 'what\n' + '#25\n' + '#33, maybe gh-100, not sure about #3\n', options, reg ); expect(msg.references).to.eql([{ action: null, owner: null, repository: null, issue: '1', raw: 'Kills #1', prefix: '#' }, { action: null, owner: null, repository: null, issue: '123', raw: ', gh-123', prefix: 'gh-' }, { action: null, owner: null, repository: null, issue: '25', raw: '#25', prefix: '#' }, { action: null, owner: null, repository: null, issue: '33', raw: '#33', prefix: '#' }, { action: null, owner: null, repository: null, issue: '100', raw: ', maybe gh-100', prefix: 'gh-' }, { action: null, owner: null, repository: null, issue: '3', raw: ', not sure about #3', prefix: '#' }]); }); it('should put everything after references in footer', function() { var msg = parser( 'feat(scope): broadcast $destroy event on scope destruction\n' + 'perf testing shows that in chrome this change adds 5-15% overhead\n' + 'when destroying 10k nested scopes where each scope has a $destroy listener\n' + 'Kills #1, #123\n' + 'what\n' + 'killed #25\n' + 'handle #33, Closes #100, Handled #3\n' + 'other', options, reg ); expect(msg.footer).to.equal('Kills #1, #123\nwhat\nkilled #25\nhandle #33, Closes #100, Handled #3\nother'); }); it('should parse properly if important notes comes after references', function() { var msg = parser( 'feat(scope): broadcast $destroy event on scope destruction\n' + 'perf testing shows that in chrome this change adds 5-15% overhead\n' + 'when destroying 10k nested scopes where each scope has a $destroy listener\n' + 'Kills #1, #123\n' + 'BREAKING AMEND: some breaking change\n', options, reg ); expect(msg.notes[0]).to.eql({ title: 'BREAKING AMEND', text: 'some breaking change' }); expect(msg.references).to.eql([{ action: 'Kills', owner: null, repository: null, issue: '1', raw: '#1', prefix: '#' }, { action: 'Kills', owner: null, repository: null, issue: '123', raw: ', #123', prefix: '#' }]); expect(msg.footer).to.equal('Kills #1, #123\nBREAKING AMEND: some breaking change'); }); it('should parse properly if important notes comes with more than one paragraphs after references', function() { var msg = parser( 'feat(scope): broadcast $destroy event on scope destruction\n' + 'perf testing shows that in chrome this change adds 5-15% overhead\n' + 'when destroying 10k nested scopes where each scope has a $destroy listener\n' + 'Kills #1, #123\n' + 'BREAKING AMEND: some breaking change\nsome other breaking change', options, reg ); expect(msg.notes[0]).to.eql({ title: 'BREAKING AMEND', text: 'some breaking change\nsome other breaking change' }); expect(msg.references).to.eql([{ action: 'Kills', owner: null, repository: null, issue: '1', raw: '#1', prefix: '#' }, { action: 'Kills', owner: null, repository: null, issue: '123', raw: ', #123', prefix: '#' }]); expect(msg.footer).to.equal('Kills #1, #123\nBREAKING AMEND: some breaking change\nsome other breaking change'); }); it('should parse properly if important notes comes after references', function() { var msg = parser( 'feat(scope): broadcast $destroy event on scope destruction\n' + 'perf testing shows that in chrome this change adds 5-15% overhead\n' + 'when destroying 10k nested scopes where each scope has a $destroy listener\n' + 'Kills gh-1, #123\n' + 'other\n' + 'BREAKING AMEND: some breaking change\n', options, reg ); expect(msg.notes[0]).to.eql({ title: 'BREAKING AMEND', text: 'some breaking change' }); expect(msg.references).to.eql([{ action: 'Kills', owner: null, repository: null, issue: '1', raw: 'gh-1', prefix: 'gh-' }, { action: 'Kills', owner: null, repository: null, issue: '123', raw: ', #123', prefix: '#' }]); expect(msg.footer).to.equal('Kills gh-1, #123\nother\nBREAKING AMEND: some breaking change'); }); }); describe('others', function() { it('should parse hash', function() { msg = parser( 'My commit message\n' + '-hash-\n' + '9b1aff905b638aa274a5fc8f88662df446d374bd', options, reg ); expect(msg.hash).to.equal('9b1aff905b638aa274a5fc8f88662df446d374bd'); }); it('should parse sideNotes', function() { msg = parser( 'My commit message\n' + '-sideNotes-\n' + 'It should warn the correct unfound file names.\n' + 'Also it should continue if one file cannot be found.\n' + 'Tests are added for these', options, reg ); expect(msg.sideNotes).to.equal('It should warn the correct unfound file names.\n' + 'Also it should continue if one file cannot be found.\n' + 'Tests are added for these'); }); it('should parse committer name and email', function() { msg = parser( 'My commit message\n' + '-committerName-\n' + 'Steve Mao\n' + '- committerEmail-\n' + 'test@github.com', options, reg ); expect(msg.committerName).to.equal('Steve Mao'); expect(msg[' committerEmail']).to.equal('test@github.com'); }); }); describe('revert', function() { it('should parse revert', function() { msg = parser( 'Revert "throw an error if a callback is passed to animate methods"\n\n' + 'This reverts commit 9bb4d6ccbe80b7704c6b7f53317ca8146bc103ca.', options, reg ); expect(msg.revert).to.eql({ header: 'throw an error if a callback is passed to animate methods', hash: '9bb4d6ccbe80b7704c6b7f53317ca8146bc103ca' }); }); it('should parse revert even if a field is missing', function() { msg = parser( 'Revert ""\n\n' + 'This reverts commit .', options, reg ); expect(msg.revert).to.eql({ header: null, hash: null }); }); }); });