Skip to content

Commit

Permalink
refactor: move util tests to separate files
Browse files Browse the repository at this point in the history
  • Loading branch information
lekterable committed Apr 23, 2020
1 parent c5f11da commit db810d5
Show file tree
Hide file tree
Showing 9 changed files with 574 additions and 553 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## Latest

### BREAKING

- group changelog entries by type c5f11dab

### Features

- add --root option b813606b
Expand Down
81 changes: 81 additions & 0 deletions src/utils/changelog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { readFile } from 'fs'
import { getCommitDetails } from './git'

export const generateReleased = previousVersion =>
new Promise((resolve, reject) =>
readFile('CHANGELOG.md', 'utf8', (err, data) => {
if (err) return reject(err)

let isLatest = false
const released = data
.split('\n')
.filter(line => {
if (line === '## Latest') {
isLatest = true

return false
}

if (isLatest && line === `## ${previousVersion}`) {
isLatest = false

return true
}

return !isLatest
})
.join('\n')

if (isLatest) {
return reject(new Error('Previous release not found in CHANGELOG'))
}

return resolve(released)
})
)

export const generateChangelog = (version, groups) => {
const changelog = groups.map(group => {
const release = getCommitDetails(group.release)
const title = version || 'Latest'
let groupChangelog = `## ${release ? release.message : title}\n\n`

const entries = Object.entries(group)

entries.sort().forEach(([title, commits]) => {
if (title === 'release') return

switch (title) {
case 'breaking':
groupChangelog += '### BREAKING\n\n'
break
case 'feat':
groupChangelog += '### Features\n\n'
break
case 'fix':
groupChangelog += '### Fixes\n\n'
break
default:
groupChangelog += '### Misc\n\n'
break
}

return commits.forEach((commit, index) => {
const { message, hash } = getCommitDetails(commit)

const isLastLine = index + 1 === commits.length
const space = isLastLine ? '\n\n' : '\n'
const line = generateLine({ message, hash }) + space

return (groupChangelog += line)
})
})

return groupChangelog
})

return changelog.join('')
}

export const generateLine = ({ message, hash }) =>
`- ${message} ${hash.slice(0, 8)}`
122 changes: 122 additions & 0 deletions src/utils/changelog.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { readFile } from 'fs'
import { generateChangelog, generateLine, generateReleased } from './changelog'

jest.mock('fs', () => ({
readFile: jest.fn()
}))

describe('changelog', () => {
beforeEach(() => jest.resetAllMocks())

describe('generateReleased', () => {
it('should reject if receives an error', async () => {
const error = 'error'
readFile.mockImplementation((_, __, cb) => cb(error))

expect(generateReleased()).rejects.toMatch(error)
})

it('should reject if there is no released but tag provided', async () => {
const error = 'Previous release not found in CHANGELOG'
const mockedInput =
'## Latest\n- feat: include changelog in the releases 2da21c56\n- test: add utils tests 217b25d0'
readFile.mockImplementation((_, __, cb) => cb(null, mockedInput))

expect(generateReleased('2.2.2')).rejects.toThrow(error)
})

it('should generate released', async () => {
const mockedInput =
'## Latest\n- feat: include changelog in the releases 2da21c56\n- test: add utils tests 217b25d0\n## 2.2.2\n- feat: add feature 2da21c56'
const mockedOutput = '## 2.2.2\n- feat: add feature 2da21c56'
readFile.mockImplementation((_, __, cb) => cb(null, mockedInput))
const released = await generateReleased('2.2.2')

expect(released).toBe(mockedOutput)
})
})

describe('generateChangelog', () => {
it('should generate changelog', () => {
const mockedInput = [
{
breaking: [
'b2f5901922505efbfb6dd684252e8df0cdffeeb2 feat!: add new api',
'2ea04355c1e81c5088eeabc6e242fb1ade978524 feat!: deprecate function'
],
feat: [
'aa805ce71ee103965ce3db46d4f6ed2658efd08d feat: add option to write to local CHANGELOG file'
],
misc: [
'bffc2f9e8da1c7ac133689bc9cd14494f3be08e3 refactor: extract line generating logic to function and promisify exec'
]
},
{
release:
'f2191200bf7b6e5eec3d61fcef9eb756e0129cfb chore(release): 0.1.0',
fix: [
'b2f5901922505efbfb6dd684252e8df0cdffeeb2 fix: support other conventions'
],
feat: [
'4e02179cae1234d7083036024080a3f25fcb52c2 feat: add execute release feature'
],
misc: [
'4e02179cae1234d7083036024080a3f25fcb52c2 chore: update dependencies'
]
}
]
const mockedOutput =
'## Latest\n\n### BREAKING\n\n- add new api b2f59019\n- deprecate function 2ea04355\n\n### Features\n\n- add option to write to local CHANGELOG file aa805ce7\n\n### Misc\n\n- extract line generating logic to function and promisify exec bffc2f9e\n\n## 0.1.0\n\n### Features\n\n- add execute release feature 4e02179c\n\n### Fixes\n\n- support other conventions b2f59019\n\n### Misc\n\n- update dependencies 4e02179c\n\n'

expect(generateChangelog(null, mockedInput)).toBe(mockedOutput)
})

it('should generate changelog with version', () => {
const mockedInput = [
{
breaking: [
'b2f5901922505efbfb6dd684252e8df0cdffeeb2 feat!: add new api',
'2ea04355c1e81c5088eeabc6e242fb1ade978524 feat!: deprecate function'
],
feat: [
'aa805ce71ee103965ce3db46d4f6ed2658efd08d feat: add option to write to local CHANGELOG file'
],
misc: [
'bffc2f9e8da1c7ac133689bc9cd14494f3be08e3 refactor: extract line generating logic to function and promisify exec'
]
},
{
release:
'f2191200bf7b6e5eec3d61fcef9eb756e0129cfb chore(release): 0.1.0',
fix: [
'b2f5901922505efbfb6dd684252e8df0cdffeeb2 fix: support other conventions'
],
feat: [
'4e02179cae1234d7083036024080a3f25fcb52c2 feat: add execute release feature'
],
misc: [
'4e02179cae1234d7083036024080a3f25fcb52c2 chore: update dependencies'
]
}
]
const mockedOutput =
'## 2.0.0\n\n### BREAKING\n\n- add new api b2f59019\n- deprecate function 2ea04355\n\n### Features\n\n- add option to write to local CHANGELOG file aa805ce7\n\n### Misc\n\n- extract line generating logic to function and promisify exec bffc2f9e\n\n## 0.1.0\n\n### Features\n\n- add execute release feature 4e02179c\n\n### Fixes\n\n- support other conventions b2f59019\n\n### Misc\n\n- update dependencies 4e02179c\n\n'

expect(generateChangelog('2.0.0', mockedInput)).toBe(mockedOutput)
})
})

describe('generateLine', () => {
it('should generate line', () => {
const mockedInput = {
message: 'generate changelog',
hash: 'b2f5901922505efbfb6dd684252e8df0cdffeeb2'
}
const mockedOutput = '- generate changelog b2f59019'

const line = generateLine(mockedInput)

expect(line).toEqual(mockedOutput)
})
})
})
42 changes: 42 additions & 0 deletions src/utils/git.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { execAsync } from './misc'

export const getCommits = async tag => {
const query = tag
? `git log --format="%H %s" ${tag}..`
: 'git log --format="%H %s"'
const commits = await execAsync(query)

return commits.split('\n').filter(commit => commit)
}

export const getLatestTag = async () => {
const latestTag = await execAsync('git tag | tail -n 1')

return latestTag ? latestTag.replace('\n', '') : null
}

export const getCommitDetails = commit => {
if (!commit) return null

const {
groups: { hash, title }
} = commit.match(/(?<hash>.{40}) (?<title>.*)/)

const commitDetails = title.match(
/(?<type>[\w ]*)(?:\((?<scope>[\w ]*)\))?(?<breaking>!)?: (?<message>.*)/
)

if (!commitDetails) return title

const {
groups: { type, scope, message, breaking }
} = commitDetails

return { hash, title, type, scope, message, breaking }
}

export const commitRelease = async version => {
await execAsync('git add CHANGELOG.md package.json package-lock.json')
await execAsync(`git commit -m 'chore(release): ${version}'`)
await execAsync(`git tag ${version}`)
}
Loading

0 comments on commit db810d5

Please sign in to comment.