New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor: port parse to typescript #764
Changes from 3 commits
46596a3
0974153
8cd3051
8f08e67
c161bb0
7767d51
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,40 +1,40 @@ | ||
import importFrom from 'import-from'; | ||
import test from 'ava'; | ||
import parse from '.'; | ||
|
||
test('throws when called without params', async t => { | ||
const error = await t.throws(parse()); | ||
t.is(error.message, 'Expected a raw commit'); | ||
test('throws when called without params', () => { | ||
expect(parse()).rejects.toThrowError('Expected a raw commit'); | ||
}); | ||
|
||
test('throws when called with empty message', async t => { | ||
const error = await t.throws(parse()); | ||
t.is(error.message, 'Expected a raw commit'); | ||
test('throws when called with empty message', () => { | ||
expect(parse()).rejects.toThrowError('Expected a raw commit'); | ||
}); | ||
|
||
test('returns object with raw message', async t => { | ||
test('returns object with raw message', async () => { | ||
const message = 'type(scope): subject'; | ||
const actual = await parse(message); | ||
t.is(actual.raw, message); | ||
|
||
expect(actual).toHaveProperty('raw', message); | ||
}); | ||
|
||
test('calls parser with message and passed options', async t => { | ||
test('calls parser with message and passed options', async () => { | ||
const message = 'message'; | ||
|
||
await parse(message, m => { | ||
t.is(message, m); | ||
expect.assertions(1); | ||
await parse(message, (m: string) => { | ||
expect(m).toBe(message); | ||
return {}; | ||
}); | ||
}); | ||
|
||
test('passes object up from parser function', async t => { | ||
test('passes object up from parser function', async () => { | ||
const message = 'message'; | ||
const result = {}; | ||
const actual = await parse(message, () => result); | ||
t.is(actual, result); | ||
|
||
expect(actual).toBe(result); | ||
}); | ||
|
||
test('returns object with expected keys', async t => { | ||
test('returns object with expected keys', async () => { | ||
const message = 'message'; | ||
const actual = await parse(message); | ||
const expected = { | ||
|
@@ -51,10 +51,11 @@ test('returns object with expected keys', async t => { | |
subject: null, | ||
type: null | ||
}; | ||
t.deepEqual(actual, expected); | ||
|
||
expect(actual).toMatchObject(expected); | ||
}); | ||
|
||
test('uses angular grammar', async t => { | ||
test('uses angular grammar', async () => { | ||
const message = 'type(scope): subject'; | ||
const actual = await parse(message); | ||
const expected = { | ||
|
@@ -71,14 +72,15 @@ test('uses angular grammar', async t => { | |
subject: 'subject', | ||
type: 'type' | ||
}; | ||
t.deepEqual(actual, expected); | ||
|
||
expect(actual).toMatchObject(expected); | ||
}); | ||
|
||
test('uses custom opts parser', async t => { | ||
test('uses custom opts parser', async () => { | ||
const message = 'type(scope)-subject'; | ||
const changelogOpts = await importFrom( | ||
process.cwd(), | ||
'./fixtures/parser-preset/conventional-changelog-custom' | ||
const changelogOpts: any = await importFrom( | ||
__dirname, | ||
'../fixtures/parser-preset/conventional-changelog-custom.js' | ||
); | ||
const actual = await parse(message, undefined, changelogOpts.parserOpts); | ||
const expected = { | ||
|
@@ -95,10 +97,11 @@ test('uses custom opts parser', async t => { | |
subject: 'subject', | ||
type: 'type' | ||
}; | ||
t.deepEqual(actual, expected); | ||
|
||
expect(actual).toMatchObject(expected); | ||
}); | ||
|
||
test('does not merge array properties with custom opts', async t => { | ||
test('does not merge array properties with custom opts', async () => { | ||
const message = 'type: subject'; | ||
const actual = await parse(message, undefined, { | ||
headerPattern: /^(.*):\s(.*)$/, | ||
|
@@ -117,54 +120,59 @@ test('does not merge array properties with custom opts', async t => { | |
subject: 'subject', | ||
type: 'type' | ||
}; | ||
t.deepEqual(actual, expected); | ||
|
||
expect(actual).toMatchObject(expected); | ||
}); | ||
|
||
test('supports scopes with /', async t => { | ||
test('supports scopes with /', async () => { | ||
const message = 'type(some/scope): subject'; | ||
const actual = await parse(message); | ||
t.is(actual.scope, 'some/scope'); | ||
t.is(actual.subject, 'subject'); | ||
|
||
expect(actual.scope).toBe('some/scope'); | ||
expect(actual.subject).toBe('subject'); | ||
}); | ||
|
||
test('supports scopes with / and empty parserOpts', async t => { | ||
test('supports scopes with / and empty parserOpts', async () => { | ||
const message = 'type(some/scope): subject'; | ||
const actual = await parse(message, undefined, {}); | ||
t.is(actual.scope, 'some/scope'); | ||
t.is(actual.subject, 'subject'); | ||
|
||
expect(actual.scope).toBe('some/scope'); | ||
expect(actual.subject).toBe('subject'); | ||
}); | ||
|
||
test('ignores comments', async t => { | ||
test('ignores comments', async () => { | ||
const message = 'type(some/scope): subject\n# some comment'; | ||
const changelogOpts = await importFrom( | ||
const changelogOpts: any = await importFrom( | ||
process.cwd(), | ||
'conventional-changelog-angular' | ||
); | ||
const opts = Object.assign({}, changelogOpts.parserOpts, { | ||
commentChar: '#' | ||
}); | ||
const actual = await parse(message, undefined, opts); | ||
t.is(actual.body, null); | ||
t.is(actual.footer, null); | ||
t.is(actual.subject, 'subject'); | ||
|
||
expect(actual.body).toBe(null); | ||
expect(actual.footer).toBe(null); | ||
expect(actual.subject).toBe('subject'); | ||
}); | ||
|
||
test('registers inline #', async t => { | ||
test('registers inline #', async () => { | ||
const message = | ||
'type(some/scope): subject #reference\n# some comment\nthings #reference'; | ||
const changelogOpts = await importFrom( | ||
const changelogOpts: any = await importFrom( | ||
process.cwd(), | ||
'conventional-changelog-angular' | ||
); | ||
const opts = Object.assign({}, changelogOpts.parserOpts, { | ||
commentChar: '#' | ||
}); | ||
const actual = await parse(message, undefined, opts); | ||
t.is(actual.subject, 'subject #reference'); | ||
t.is(actual.body, 'things #reference'); | ||
|
||
expect(actual.subject).toBe('subject #reference'); | ||
expect(actual.body).toBe('things #reference'); | ||
}); | ||
|
||
test('parses references leading subject', async t => { | ||
test('parses references leading subject', async () => { | ||
const message = '#1 some subject'; | ||
const opts = await importFrom( | ||
process.cwd(), | ||
|
@@ -173,17 +181,18 @@ test('parses references leading subject', async t => { | |
const { | ||
references: [actual] | ||
} = await parse(message, undefined, opts); | ||
t.is(actual.issue, '1'); | ||
|
||
expect(actual.issue).toBe('1'); | ||
}); | ||
|
||
test('parses custom references', async t => { | ||
test('parses custom references', async () => { | ||
const message = '#1 some subject PREFIX-2'; | ||
const {references} = await parse(message, undefined, { | ||
issuePrefixes: ['PREFIX-'] | ||
}); | ||
|
||
t.falsy(references.find(ref => ref.issue === '1')); | ||
t.deepEqual(references.find(ref => ref.issue === '2'), { | ||
expect(references.find(ref => ref.issue === '1')).toBeFalsy(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we don't add types to the parser output, you can't use expect(references.find((ref: any) => ref.issue === '1')).toBeFalsy(); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding an optional type parameter defaulting to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if I understand this fully, do you want to add a different typing to the existing |
||
expect(references.find(ref => ref.issue === '2')).toMatchObject({ | ||
action: null, | ||
issue: '2', | ||
owner: null, | ||
|
@@ -193,44 +202,44 @@ test('parses custom references', async t => { | |
}); | ||
}); | ||
|
||
test('uses permissive default regex without parser opts', async t => { | ||
test('uses permissive default regex without parser opts', async () => { | ||
const message = 'chore(component,demo): bump'; | ||
const actual = await parse(message); | ||
|
||
t.is(actual.scope, 'component,demo'); | ||
expect(actual.scope).toBe('component,demo'); | ||
}); | ||
|
||
test('uses permissive default regex with other parser opts', async t => { | ||
test('uses permissive default regex with other parser opts', async () => { | ||
const message = 'chore(component,demo): bump'; | ||
const actual = await parse(message, undefined, {commentChar: '#'}); | ||
|
||
t.is(actual.scope, 'component,demo'); | ||
expect(actual.scope).toBe('component,demo'); | ||
}); | ||
|
||
test('uses restrictive default regex in passed parser opts', async t => { | ||
test('uses restrictive default regex in passed parser opts', async () => { | ||
const message = 'chore(component,demo): bump'; | ||
const actual = await parse(message, undefined, { | ||
headerPattern: /^(\w*)(?:\(([a-z]*)\))?: (.*)$/ | ||
}); | ||
|
||
t.is(actual.subject, null); | ||
t.is(actual.scope, null); | ||
expect(actual.subject).toBe(null); | ||
expect(actual.scope).toBe(null); | ||
}); | ||
|
||
test('works with chinese scope by default', async t => { | ||
test('works with chinese scope by default', async () => { | ||
const message = 'fix(面试评价): 测试'; | ||
const actual = await parse(message, undefined, {commentChar: '#'}); | ||
|
||
t.not(actual.subject, null); | ||
t.not(actual.scope, null); | ||
expect(actual.subject).not.toBe(null); | ||
expect(actual.scope).not.toBe(null); | ||
}); | ||
|
||
test('does not work with chinese scopes with incompatible pattern', async t => { | ||
test('does not work with chinese scopes with incompatible pattern', async () => { | ||
const message = 'fix(面试评价): 测试'; | ||
const actual = await parse(message, undefined, { | ||
headerPattern: /^(\w*)(?:\(([a-z]*)\))?: (.*)$/ | ||
}); | ||
|
||
t.is(actual.subject, null); | ||
t.is(actual.scope, null); | ||
expect(actual.subject).toBe(null); | ||
expect(actual.scope).toBe(null); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
export interface Commit { | ||
raw: string; | ||
header: string; | ||
type: string | null; | ||
scope: string | null; | ||
subject: string | null; | ||
body: string | null; | ||
footer: string | null; | ||
mentions: string[]; | ||
notes: CommitNote[]; | ||
references: CommitReference[]; | ||
revert: any; | ||
merge: any; | ||
} | ||
|
||
export interface CommitNote { | ||
title: string; | ||
text: string; | ||
} | ||
|
||
export interface CommitReference { | ||
raw: string; | ||
prefix: string; | ||
action: string | null; | ||
owner: string | null; | ||
repository: string | null; | ||
issue: string | null; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"extends": "../../tsconfig.shared.json", | ||
"compilerOptions": { | ||
"composite": true, | ||
"rootDir": "./src", | ||
"outDir": "./lib" | ||
}, | ||
"include": [ | ||
"./src" | ||
], | ||
"exclude": [ | ||
"./src/**/*.test.ts", | ||
"./lib/**/*" | ||
] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was a bit weird because we are running Jest from the root of the monorepo
process.cwd
seems to be set to that folder. In this case, we need to scope it from within this package. This works, but not sure if this is the best solution.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is fine. We might want to use fixturez for this kind of thing to decouple test files from exact relative fixture paths. If you feel eager you could give it a try here, otherwise let's go forward as is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is important for
@commitlint/load
, was doing some work on that but did not finish in time. In this instance, it might be a bit of an overkill? 😬