prsmith 1.1.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +124 -43
- package/bin/cli.js +196 -31
- package/eslint.config.js +8 -6
- package/package.json +1 -1
- package/src/ai.js +118 -0
- package/src/batch.js +219 -0
- package/src/config.js +32 -3
- package/src/formatter.js +40 -9
- package/src/github.js +153 -0
- package/src/prompts.js +118 -21
- package/src/templates.js +5 -9
- package/tests/features.test.js +253 -0
- package/tests/formatter.test.js +32 -28
package/src/prompts.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import inquirer from
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
2
|
|
|
3
3
|
export async function getReviewData(options = {}, config = {}) {
|
|
4
|
-
const defaultSeverities = [
|
|
4
|
+
const defaultSeverities = ['Critical', 'Major', 'Minor', 'Suggestion'];
|
|
5
5
|
let severities = [...defaultSeverities];
|
|
6
6
|
if (config.templates) {
|
|
7
7
|
for (const key of Object.keys(config.templates)) {
|
|
@@ -11,42 +11,139 @@ export async function getReviewData(options = {}, config = {}) {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
// 1. Gather Core Fields
|
|
15
|
+
const coreQuestions = [];
|
|
15
16
|
|
|
16
17
|
if (!options.severity) {
|
|
17
|
-
|
|
18
|
-
type:
|
|
19
|
-
name:
|
|
20
|
-
message:
|
|
18
|
+
coreQuestions.push({
|
|
19
|
+
type: 'select',
|
|
20
|
+
name: 'severity',
|
|
21
|
+
message: 'Select severity:',
|
|
21
22
|
choices: severities,
|
|
22
23
|
});
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
if (!options.title) {
|
|
26
|
-
|
|
27
|
-
type:
|
|
28
|
-
name:
|
|
29
|
-
message:
|
|
27
|
+
coreQuestions.push({
|
|
28
|
+
type: 'input',
|
|
29
|
+
name: 'title',
|
|
30
|
+
message: 'Review title:',
|
|
30
31
|
});
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
if (!options.issue) {
|
|
34
|
-
|
|
35
|
-
type:
|
|
36
|
-
name:
|
|
37
|
-
message:
|
|
35
|
+
coreQuestions.push({
|
|
36
|
+
type: 'editor',
|
|
37
|
+
name: 'issue',
|
|
38
|
+
message:
|
|
39
|
+
'Describe the issue (Vim: i to write, Esc then :wq to save & exit. Nano: Ctrl+O, Enter, Ctrl+X):',
|
|
38
40
|
});
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
if (!options.fix) {
|
|
42
|
-
|
|
43
|
-
type:
|
|
44
|
-
name:
|
|
45
|
-
message:
|
|
44
|
+
coreQuestions.push({
|
|
45
|
+
type: 'editor',
|
|
46
|
+
name: 'fix',
|
|
47
|
+
message:
|
|
48
|
+
'Suggested fix (Vim: i to write, Esc then :wq to save & exit. Nano: Ctrl+O, Enter, Ctrl+X):',
|
|
46
49
|
});
|
|
47
50
|
}
|
|
48
51
|
|
|
49
|
-
const
|
|
52
|
+
const coreAnswers =
|
|
53
|
+
coreQuestions.length > 0 ? await inquirer.prompt(coreQuestions) : {};
|
|
54
|
+
const currentData = { ...options, ...coreAnswers };
|
|
50
55
|
|
|
51
|
-
|
|
56
|
+
// 2. Gather File & Line Context
|
|
57
|
+
let fileAnswers = {};
|
|
58
|
+
if (currentData.path === undefined && currentData.line === undefined) {
|
|
59
|
+
const { addContext } = await inquirer.prompt([
|
|
60
|
+
{
|
|
61
|
+
type: 'confirm',
|
|
62
|
+
name: 'addContext',
|
|
63
|
+
message: 'Would you like to add file & line context?',
|
|
64
|
+
default: false,
|
|
65
|
+
},
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
if (addContext) {
|
|
69
|
+
fileAnswers = await inquirer.prompt([
|
|
70
|
+
{
|
|
71
|
+
type: 'input',
|
|
72
|
+
name: 'path',
|
|
73
|
+
message: 'File path (relative to repo root, e.g. src/index.js):',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
type: 'input',
|
|
77
|
+
name: 'line',
|
|
78
|
+
message: 'Line number or range (e.g. 12 or 45-50):',
|
|
79
|
+
},
|
|
80
|
+
]);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 3. Gather Before/After Code Snippets
|
|
85
|
+
let codeAnswers = {};
|
|
86
|
+
if (currentData.before === undefined && currentData.after === undefined) {
|
|
87
|
+
const { addSnippets } = await inquirer.prompt([
|
|
88
|
+
{
|
|
89
|
+
type: 'confirm',
|
|
90
|
+
name: 'addSnippets',
|
|
91
|
+
message: 'Would you like to add Before/After code snippets?',
|
|
92
|
+
default: false,
|
|
93
|
+
},
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
if (addSnippets) {
|
|
97
|
+
codeAnswers = await inquirer.prompt([
|
|
98
|
+
{
|
|
99
|
+
type: 'input',
|
|
100
|
+
name: 'lang',
|
|
101
|
+
message: 'Programming language (for markdown syntax highlighting):',
|
|
102
|
+
default: 'javascript',
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
type: 'editor',
|
|
106
|
+
name: 'before',
|
|
107
|
+
message:
|
|
108
|
+
'Original Code (Before) (Vim: i to write, Esc then :wq to save & exit. Nano: Ctrl+O, Enter, Ctrl+X):',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
type: 'editor',
|
|
112
|
+
name: 'after',
|
|
113
|
+
message:
|
|
114
|
+
'Proposed Code (After) (Vim: i to write, Esc then :wq to save & exit. Nano: Ctrl+O, Enter, Ctrl+X):',
|
|
115
|
+
},
|
|
116
|
+
]);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 4. Gather AI Polish Toggle
|
|
121
|
+
let aiAnswers = {};
|
|
122
|
+
if (currentData.ai === undefined) {
|
|
123
|
+
const hasKey = !!(
|
|
124
|
+
config.aiApiKey ||
|
|
125
|
+
process.env.GEMINI_API_KEY ||
|
|
126
|
+
process.env.OPENAI_API_KEY ||
|
|
127
|
+
process.env.GROQ_API_KEY
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const { ai } = await inquirer.prompt([
|
|
131
|
+
{
|
|
132
|
+
type: 'confirm',
|
|
133
|
+
name: 'ai',
|
|
134
|
+
message: hasKey
|
|
135
|
+
? 'Polish the description and fix with AI for a constructive, polite tone?'
|
|
136
|
+
: 'Polish with AI? (Note: Needs AI key configured in .prsmith.json or env)',
|
|
137
|
+
default: false,
|
|
138
|
+
},
|
|
139
|
+
]);
|
|
140
|
+
aiAnswers = { ai };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
...currentData,
|
|
145
|
+
...fileAnswers,
|
|
146
|
+
...codeAnswers,
|
|
147
|
+
...aiAnswers,
|
|
148
|
+
};
|
|
52
149
|
}
|
package/src/templates.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
export const templates = {
|
|
2
|
-
Critical:
|
|
3
|
-
"The current implementation introduces a critical issue.",
|
|
2
|
+
Critical: 'The current implementation introduces a critical issue.',
|
|
4
3
|
|
|
5
|
-
Major:
|
|
6
|
-
"The current implementation introduces a significant issue.",
|
|
4
|
+
Major: 'The current implementation introduces a significant issue.',
|
|
7
5
|
|
|
8
|
-
Minor:
|
|
9
|
-
"The current implementation could be improved.",
|
|
6
|
+
Minor: 'The current implementation could be improved.',
|
|
10
7
|
|
|
11
|
-
Suggestion:
|
|
12
|
-
|
|
13
|
-
};
|
|
8
|
+
Suggestion: 'The implementation works, but there may be a cleaner approach.',
|
|
9
|
+
};
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import { generateMarkdown } from '../src/formatter.js';
|
|
4
|
+
import { detectGithubRepo } from '../src/github.js';
|
|
5
|
+
import { polishText } from '../src/ai.js';
|
|
6
|
+
import { loadConfig } from '../src/config.js';
|
|
7
|
+
|
|
8
|
+
describe('v2.0.0 Features Test Suite', () => {
|
|
9
|
+
let existsSyncSpy;
|
|
10
|
+
let readFileSyncSpy;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.clearAllMocks();
|
|
14
|
+
// Spy on fs methods
|
|
15
|
+
existsSyncSpy = vi.spyOn(fs, 'existsSync');
|
|
16
|
+
readFileSyncSpy = vi.spyOn(fs, 'readFileSync');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
vi.restoreAllMocks();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('File & Line Context Deep Links', () => {
|
|
24
|
+
it('should generate standard file path text if no githubRepo is detected', () => {
|
|
25
|
+
const data = {
|
|
26
|
+
severity: 'Suggestion',
|
|
27
|
+
title: 'Optimized Loop',
|
|
28
|
+
issue: 'Loop could be faster.',
|
|
29
|
+
fix: 'Use for-i loop.',
|
|
30
|
+
path: 'src/utils/math.js',
|
|
31
|
+
line: '15-20',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
existsSyncSpy.mockReturnValue(false);
|
|
35
|
+
|
|
36
|
+
const markdown = generateMarkdown(data);
|
|
37
|
+
expect(markdown).toContain('📁 **File:** `src/utils/math.js:15-20`');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should generate a clickable GitHub deep link if githubRepo is provided', () => {
|
|
41
|
+
const data = {
|
|
42
|
+
severity: 'Suggestion',
|
|
43
|
+
title: 'Optimized Loop',
|
|
44
|
+
issue: 'Loop could be faster.',
|
|
45
|
+
fix: 'Use for-i loop.',
|
|
46
|
+
path: 'src/utils/math.js',
|
|
47
|
+
line: '15-20',
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const config = {
|
|
51
|
+
githubRepo: { owner: 'tarunyaprogrammer', repo: 'PRSmith' },
|
|
52
|
+
defaultBranch: 'main',
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const markdown = generateMarkdown(data, config);
|
|
56
|
+
expect(markdown).toContain(
|
|
57
|
+
'📁 **File:** [`src/utils/math.js:15-20`](https://github.com/tarunyaprogrammer/PRSmith/blob/main/src/utils/math.js#L15)'
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('Before / After Code Snippet Formatting', () => {
|
|
63
|
+
it('should render collapsible before and after code blocks beautifully', () => {
|
|
64
|
+
const data = {
|
|
65
|
+
severity: 'Minor',
|
|
66
|
+
title: 'Use Strict Equality',
|
|
67
|
+
issue: 'Loose equality can lead to bugs.',
|
|
68
|
+
fix: 'Use strict equality instead.',
|
|
69
|
+
lang: 'typescript',
|
|
70
|
+
before: 'if (x == y) { return; }',
|
|
71
|
+
after: 'if (x === y) { return; }',
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const markdown = generateMarkdown(data);
|
|
75
|
+
expect(markdown).toContain('<details>');
|
|
76
|
+
expect(markdown).toContain(
|
|
77
|
+
'<summary>🔍 View Code Diff / Comparison</summary>'
|
|
78
|
+
);
|
|
79
|
+
expect(markdown).toContain(
|
|
80
|
+
'**Before:**\n```typescript\nif (x == y) { return; }\n```'
|
|
81
|
+
);
|
|
82
|
+
expect(markdown).toContain(
|
|
83
|
+
'**After:**\n```typescript\nif (x === y) { return; }\n```'
|
|
84
|
+
);
|
|
85
|
+
expect(markdown).toContain('</details>');
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('Git Config Parsing (detectGithubRepo)', () => {
|
|
90
|
+
it('should correctly parse HTTPS git urls from config file', () => {
|
|
91
|
+
existsSyncSpy.mockReturnValue(true);
|
|
92
|
+
readFileSyncSpy.mockReturnValue(`
|
|
93
|
+
[core]
|
|
94
|
+
repositoryformatversion = 0
|
|
95
|
+
filemode = true
|
|
96
|
+
[remote "origin"]
|
|
97
|
+
url = https://github.com/tarunyaprogrammer/PRSmith.git
|
|
98
|
+
fetch = +refs/heads/*:refs/remotes/origin/*
|
|
99
|
+
`);
|
|
100
|
+
|
|
101
|
+
const repo = detectGithubRepo();
|
|
102
|
+
expect(repo).toEqual({ owner: 'tarunyaprogrammer', repo: 'PRSmith' });
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should correctly parse SSH git urls from config file', () => {
|
|
106
|
+
existsSyncSpy.mockReturnValue(true);
|
|
107
|
+
readFileSyncSpy.mockReturnValue(`
|
|
108
|
+
[remote "origin"]
|
|
109
|
+
url = git@github.com:google/antigravity.git
|
|
110
|
+
`);
|
|
111
|
+
|
|
112
|
+
const repo = detectGithubRepo();
|
|
113
|
+
expect(repo).toEqual({ owner: 'google', repo: 'antigravity' });
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('AI Polish (polishText) HTTP mock', () => {
|
|
118
|
+
beforeEach(() => {
|
|
119
|
+
global.fetch = vi.fn();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should fallback to original text if no key is provided', async () => {
|
|
123
|
+
const originalText = 'This is a harsh comment!';
|
|
124
|
+
const polished = await polishText(
|
|
125
|
+
originalText,
|
|
126
|
+
'Problem Description',
|
|
127
|
+
{}
|
|
128
|
+
);
|
|
129
|
+
expect(polished).toBe(originalText);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should invoke Gemini API correctly and return polished content', async () => {
|
|
133
|
+
const originalText = 'Your code is slow.';
|
|
134
|
+
const responseMock = {
|
|
135
|
+
ok: true,
|
|
136
|
+
json: async () => ({
|
|
137
|
+
candidates: [
|
|
138
|
+
{
|
|
139
|
+
content: {
|
|
140
|
+
parts: [
|
|
141
|
+
{
|
|
142
|
+
text: 'The current implementation has room for performance optimization.',
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
}),
|
|
149
|
+
};
|
|
150
|
+
global.fetch.mockResolvedValue(responseMock);
|
|
151
|
+
|
|
152
|
+
const config = {
|
|
153
|
+
aiProvider: 'gemini',
|
|
154
|
+
aiApiKey: 'mock-key',
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const polished = await polishText(
|
|
158
|
+
originalText,
|
|
159
|
+
'Problem Description',
|
|
160
|
+
config
|
|
161
|
+
);
|
|
162
|
+
expect(polished).toBe(
|
|
163
|
+
'The current implementation has room for performance optimization.'
|
|
164
|
+
);
|
|
165
|
+
expect(global.fetch).toHaveBeenCalledWith(
|
|
166
|
+
expect.stringContaining('generativelanguage.googleapis.com'),
|
|
167
|
+
expect.objectContaining({
|
|
168
|
+
method: 'POST',
|
|
169
|
+
body: expect.stringContaining('Your code is slow.'),
|
|
170
|
+
})
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should invoke OpenAI API correctly and return polished content', async () => {
|
|
175
|
+
const originalText = 'Close the resource.';
|
|
176
|
+
const responseMock = {
|
|
177
|
+
ok: true,
|
|
178
|
+
json: async () => ({
|
|
179
|
+
choices: [
|
|
180
|
+
{
|
|
181
|
+
message: {
|
|
182
|
+
content:
|
|
183
|
+
'It is highly recommended to properly release the resource in a finally block.',
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
}),
|
|
188
|
+
};
|
|
189
|
+
global.fetch.mockResolvedValue(responseMock);
|
|
190
|
+
|
|
191
|
+
const config = {
|
|
192
|
+
aiProvider: 'openai',
|
|
193
|
+
aiApiKey: 'mock-key-openai',
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const polished = await polishText(originalText, 'Suggested Fix', config);
|
|
197
|
+
expect(polished).toBe(
|
|
198
|
+
'It is highly recommended to properly release the resource in a finally block.'
|
|
199
|
+
);
|
|
200
|
+
expect(global.fetch).toHaveBeenCalledWith(
|
|
201
|
+
'https://api.openai.com/v1/chat/completions',
|
|
202
|
+
expect.objectContaining({
|
|
203
|
+
method: 'POST',
|
|
204
|
+
headers: expect.objectContaining({
|
|
205
|
+
Authorization: 'Bearer mock-key-openai',
|
|
206
|
+
}),
|
|
207
|
+
})
|
|
208
|
+
);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe('Editor Config Guardrails', () => {
|
|
213
|
+
it('should dynamically set process.env.EDITOR to nano on non-windows systems if not defined', () => {
|
|
214
|
+
const originalPlatform = Object.getOwnPropertyDescriptor(
|
|
215
|
+
process,
|
|
216
|
+
'platform'
|
|
217
|
+
);
|
|
218
|
+
const originalEditor = process.env.EDITOR;
|
|
219
|
+
const originalVisual = process.env.VISUAL;
|
|
220
|
+
|
|
221
|
+
delete process.env.EDITOR;
|
|
222
|
+
delete process.env.VISUAL;
|
|
223
|
+
|
|
224
|
+
Object.defineProperty(process, 'platform', {
|
|
225
|
+
value: 'darwin',
|
|
226
|
+
configurable: true,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
existsSyncSpy.mockReturnValue(false);
|
|
230
|
+
|
|
231
|
+
loadConfig();
|
|
232
|
+
|
|
233
|
+
expect(process.env.EDITOR).toBe('nano');
|
|
234
|
+
|
|
235
|
+
// Restore original state
|
|
236
|
+
Object.defineProperty(process, 'platform', originalPlatform);
|
|
237
|
+
if (originalEditor !== undefined) process.env.EDITOR = originalEditor;
|
|
238
|
+
if (originalVisual !== undefined) process.env.VISUAL = originalVisual;
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should respect editor defined in config', () => {
|
|
242
|
+
const originalEditor = process.env.EDITOR;
|
|
243
|
+
existsSyncSpy.mockReturnValue(true);
|
|
244
|
+
readFileSyncSpy.mockReturnValue(JSON.stringify({ editor: 'vim-mock' }));
|
|
245
|
+
|
|
246
|
+
loadConfig();
|
|
247
|
+
|
|
248
|
+
expect(process.env.EDITOR).toBe('vim-mock');
|
|
249
|
+
|
|
250
|
+
if (originalEditor !== undefined) process.env.EDITOR = originalEditor;
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
});
|
package/tests/formatter.test.js
CHANGED
|
@@ -1,51 +1,55 @@
|
|
|
1
|
-
import { describe, it, expect } from
|
|
2
|
-
import { generateMarkdown } from
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { generateMarkdown } from '../src/formatter.js';
|
|
3
3
|
|
|
4
|
-
describe(
|
|
5
|
-
it(
|
|
4
|
+
describe('formatter.js', () => {
|
|
5
|
+
it('should generate proper markdown for Critical severity', () => {
|
|
6
6
|
const data = {
|
|
7
|
-
severity:
|
|
8
|
-
title:
|
|
9
|
-
issue:
|
|
10
|
-
fix:
|
|
7
|
+
severity: 'Critical',
|
|
8
|
+
title: 'Memory Leak',
|
|
9
|
+
issue: 'The connection is never closed.',
|
|
10
|
+
fix: 'Add a finally block to close the connection.',
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
const markdown = generateMarkdown(data);
|
|
14
|
-
|
|
15
|
-
expect(markdown).toContain(
|
|
16
|
-
expect(markdown).toContain(
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
|
|
15
|
+
expect(markdown).toContain('### Critical: Memory Leak');
|
|
16
|
+
expect(markdown).toContain(
|
|
17
|
+
'The current implementation introduces a critical issue.'
|
|
18
|
+
);
|
|
19
|
+
expect(markdown).toContain('The connection is never closed.');
|
|
20
|
+
expect(markdown).toContain('Add a finally block to close the connection.');
|
|
19
21
|
});
|
|
20
22
|
|
|
21
|
-
it(
|
|
23
|
+
it('should fallback to a default intro if severity is unknown', () => {
|
|
22
24
|
const data = {
|
|
23
|
-
severity:
|
|
24
|
-
title:
|
|
25
|
-
issue:
|
|
26
|
-
fix:
|
|
25
|
+
severity: 'Unknown',
|
|
26
|
+
title: 'Something',
|
|
27
|
+
issue: 'Bad code.',
|
|
28
|
+
fix: 'Fix code.',
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
const markdown = generateMarkdown(data);
|
|
30
|
-
expect(markdown).toContain(
|
|
32
|
+
expect(markdown).toContain(
|
|
33
|
+
'The current implementation requires attention.'
|
|
34
|
+
);
|
|
31
35
|
});
|
|
32
36
|
|
|
33
|
-
it(
|
|
37
|
+
it('should use custom templates when passed via config', () => {
|
|
34
38
|
const data = {
|
|
35
|
-
severity:
|
|
36
|
-
title:
|
|
37
|
-
issue:
|
|
38
|
-
fix:
|
|
39
|
+
severity: 'Nitpick',
|
|
40
|
+
title: 'Formatting',
|
|
41
|
+
issue: 'Indentation is off.',
|
|
42
|
+
fix: 'Fix indentation.',
|
|
39
43
|
};
|
|
40
44
|
|
|
41
45
|
const config = {
|
|
42
46
|
templates: {
|
|
43
|
-
Nitpick:
|
|
44
|
-
}
|
|
47
|
+
Nitpick: 'This is a tiny nitpick.',
|
|
48
|
+
},
|
|
45
49
|
};
|
|
46
50
|
|
|
47
51
|
const markdown = generateMarkdown(data, config);
|
|
48
|
-
expect(markdown).toContain(
|
|
49
|
-
expect(markdown).toContain(
|
|
52
|
+
expect(markdown).toContain('### Nitpick: Formatting');
|
|
53
|
+
expect(markdown).toContain('This is a tiny nitpick.');
|
|
50
54
|
});
|
|
51
55
|
});
|