UNPKG

11.2 kBJavaScriptView Raw
1import test from 'bron';
2import { strict as assert } from 'assert';
3import fs from 'fs';
4import path from 'path';
5import { EOL } from 'os';
6import sh from 'shelljs';
7import tmp from 'tmp';
8import semver from 'semver';
9import runTasks from 'release-it';
10
11sh.config.silent = true;
12
13try {
14 fs.unlinkSync('CHANGES.md');
15} catch (error) {}
16
17const noop = () => {};
18const log = {
19 log: noop,
20 error: noop,
21 verbose: noop,
22 info: noop,
23 obtrusive: noop,
24 exec: noop,
25 warn: noop,
26 preview: noop
27};
28
29const namespace = 'conventional-changelog';
30const { pathname } = new URL('./index.js', import.meta.url);
31const preset = { name: 'angular' };
32
33const getOptions = options => [
34 {
35 ci: true,
36 git: { commit: false, tag: false, push: false, requireUpstream: false },
37 plugins: { [pathname]: [namespace, options] }
38 },
39 { log }
40];
41
42const mkTmpDir = () => {
43 const dir = tmp.dirSync({ prefix: namespace });
44 return dir.name;
45};
46
47const add = (type, file) => {
48 sh.ShellString(file).toEnd(file);
49 sh.exec(`git add ${file}`);
50 sh.exec(`git commit -m "${type}(${file}): ${type} ${file}"`);
51};
52
53const setup = () => {
54 const dir = mkTmpDir();
55 sh.pushd(dir);
56 sh.exec(`git init .`);
57 add('fix', 'foo');
58 return { dir };
59};
60
61const date = /\([0-9]{4}-[0-9]{2}-[0-9]{2}\)/.source;
62const sha = /[0-9a-f]{7}/.source;
63const level = (from, to) => `${/patch/.test(semver.diff(from, to)) ? '##' : '#'}`;
64const header = (from, to, suffix = '') =>
65 `${level(from, to)} \\[${to}\\]\\(/compare/${from}${suffix}...${to}${suffix}\\) ${date}`;
66const features = EOL + EOL + EOL + '### Features' + EOL;
67const fixes = EOL + EOL + EOL + '### Bug Fixes' + EOL;
68const commit = (type, name) => EOL + `\\* \\*\\*${name}:\\*\\* ${type} ${name} ${sha}`;
69
70const nl = value => value.split(/\r\n|\r|\n/g).join(EOL);
71
72test('should generate changelog using recommended bump (minor)', async () => {
73 setup();
74
75 sh.exec(`git tag 1.0.0`);
76 add('fix', 'bar');
77 add('feat', 'baz');
78
79 const options = getOptions({ preset });
80 const { changelog } = await runTasks(...options);
81 const title = header('1.0.0', '1.1.0');
82 const bar = commit('fix', 'bar');
83 const baz = commit('feat', 'baz');
84 assert.match(nl(changelog), new RegExp('^' + title + fixes + bar + features + baz + '$'));
85});
86
87test('should generate changelog using recommended bump (patch)', async () => {
88 setup();
89
90 sh.exec(`git tag 1.0.0`);
91 add('fix', 'bar');
92 add('fix', 'baz');
93
94 const options = getOptions({ preset });
95 const { changelog } = await runTasks(...options);
96 const title = header('1.0.0', '1.0.1');
97 const bar = commit('fix', 'bar');
98 const baz = commit('fix', 'baz');
99 assert.match(nl(changelog), new RegExp('^' + title + fixes + bar + baz + '$'));
100});
101
102test('should support tag suffix', async () => {
103 setup();
104
105 const latestVersion = '2.0.0';
106 const suffix = '-next';
107 const latestTag = latestVersion + suffix;
108
109 sh.exec(`git tag ${latestTag}`);
110 add('fix', 'bar');
111
112 const [config, container] = getOptions({ preset });
113 config.git.tagName = `\${version}${suffix}`;
114 const { changelog, version } = await runTasks(config, container);
115 assert.match(
116 nl(changelog),
117 // release-it supports tag suffix/template, but conventional-changelog does not so the title will not contain it:
118 /^## \[2\.0\.1\]\(\/compare\/2\.0\.0-next\.\.\.2\.0\.1-next\) \([0-9]{4}-[0-9]{2}-[0-9]{2}\)\s*### Bug Fixes\s*\* \*\*bar:\*\* fix bar [0-9a-f]{7}$/
119 );
120 const title = header(latestVersion, version, suffix);
121 const bar = commit('fix', 'bar');
122 assert.match(nl(changelog), new RegExp('^' + title + fixes + bar + '$'));
123});
124
125test('should respect --no-increment and return previous, identical changelog', async () => {
126 setup();
127
128 sh.exec(`git tag 1.0.0`);
129 add('feat', 'bar');
130 add('fix', 'baz');
131 sh.exec(`git tag 1.1.0`);
132 add('fix', 'bar');
133 add('feat', 'baz');
134 sh.exec(`git tag 1.2.0`);
135
136 const [config, container] = getOptions({ preset });
137 config.increment = false;
138 const { changelog } = await runTasks(config, container);
139 const title = header('1.1.0', '1.2.0');
140 const bar = commit('fix', 'bar');
141 const baz = commit('feat', 'baz');
142 assert.match(nl(changelog), new RegExp('^' + title + fixes + bar + features + baz + '$'));
143});
144
145test('should ignore recommended bump (option)', async () => {
146 setup();
147 sh.exec(`git tag 1.0.0`);
148 add('feat', 'baz');
149
150 const options = getOptions({ preset, ignoreRecommendedBump: true });
151 const { version } = await runTasks(...options);
152 assert.equal(version, '1.0.1');
153});
154
155test('should use provided pre-release id', async t => {
156 setup();
157 sh.exec(`git tag 1.0.0`);
158 add('feat', 'baz');
159
160 const [config, container] = getOptions({ preset });
161 config.preRelease = 'alpha';
162 const { version } = await runTasks(config, container);
163 assert.equal(version, '1.1.0-alpha.0');
164});
165
166test('should use provided pre-release id (pre-release continuation)', async t => {
167 setup();
168 sh.exec(`git tag 1.0.1-alpha.0`);
169 add('feat', 'baz');
170
171 const [config, container] = getOptions({ preset });
172 config.preRelease = 'alpha';
173 const { version } = await runTasks(config, container);
174 assert.equal(version, '1.0.1-alpha.1');
175});
176
177test('should use provided pre-release id (next pre-release)', async t => {
178 setup();
179 sh.exec(`git tag 1.1.0-alpha.1`);
180
181 const [config, container] = getOptions({ preset });
182 config.preRelease = 'beta';
183 const { version } = await runTasks(config, container);
184 assert.equal(version, '1.1.0-beta.0');
185});
186
187test('should use recommended bump (after pre-rerelease)', async t => {
188 setup();
189 sh.exec(`git tag 1.0.1-beta.0`);
190 add('feat', 'baz');
191
192 const options = getOptions({ preset });
193 const { version } = await runTasks(...options);
194 assert.equal(version, '1.1.0');
195});
196
197test('should follow strict semver (pre-release continuation)', async t => {
198 setup();
199 sh.exec(`git tag 1.1.0-alpha.0`);
200 add('feat', 'baz');
201
202 const [config, container] = getOptions({ preset, strictSemVer: true });
203 config.preRelease = 'alpha';
204 const { version } = await runTasks(config, container);
205 assert.equal(version, '1.2.0-alpha.0');
206});
207
208test('should follow strict semver (pre-release continuation, conventionalcommits)', async t => {
209 setup();
210 sh.exec(`git tag 2.0.1-alpha.0`);
211 sh.ShellString('file').toEnd('file');
212 sh.exec(`git add file`);
213 sh.exec(`git commit -m "feat: new feature"`);
214
215 const [config, container] = getOptions({
216 preset: { name: 'conventionalcommits' },
217 strictSemVer: true,
218 writerOpts: {},
219 parserOpts: {}
220 });
221 config.preRelease = 'alpha';
222 const { version } = await runTasks(config, container);
223 assert.equal(version, '2.1.0-alpha.0');
224});
225
226test('should use provided increment', async () => {
227 setup();
228 sh.exec(`git tag 1.0.0`);
229
230 const [config, container] = getOptions({ preset });
231 config.increment = 'major';
232 const { version } = await runTasks(config, container);
233 assert.equal(version, '2.0.0');
234});
235
236test('should use provided version (ignore recommended bump)', async () => {
237 setup();
238
239 const [config, container] = getOptions({ preset });
240 config.increment = '1.2.3';
241 const { version } = await runTasks(config, container);
242 assert.equal(version, '1.2.3');
243});
244
245test('should not throw with Git plugin disabled', async () => {
246 setup();
247
248 const [config, container] = getOptions({ preset });
249 config.git = false;
250 const { version, changelog } = await runTasks(config, container);
251 assert.equal(version, '0.0.1');
252 const title = `## 0.0.1 ${date}`;
253 const fix = commit('fix', 'foo');
254 assert.match(nl(changelog), new RegExp(title + fixes + fix));
255});
256
257test(`should write and update infile`, async () => {
258 const { dir } = setup();
259 sh.exec(`git tag 1.0.0`);
260 add('fix', 'foo');
261 add('feat', 'bar');
262
263 const h = 'The header' + EOL + EOL + 'The subheader';
264 const infile = path.join(dir, 'CHANGES.md');
265 const [config, container] = getOptions({ preset, infile, header: h });
266 config.git.tag = true;
267 await runTasks(config, container);
268 const changelog = fs.readFileSync(infile).toString();
269 const title = header('1.0.0', '1.1.0');
270 const fix1 = commit('fix', 'foo');
271 const feat1 = commit('feat', 'bar');
272 const first = title + fixes + fix1 + features + feat1;
273 assert.match(nl(changelog), new RegExp('^' + h + EOL + EOL + first + EOL + '$'));
274 {
275 add('fix', 'bar');
276 add('fix', 'baz');
277
278 const options = getOptions({ preset, infile, header: h });
279 await runTasks(...options);
280 const changelog = fs.readFileSync(infile).toString();
281 const title2 = header('1.1.0', '1.1.1');
282 const fix2 = commit('fix', 'bar');
283 const fix3 = commit('fix', 'baz');
284 const second = title2 + fixes + fix2 + fix3;
285 assert.match(nl(changelog), new RegExp('^' + h + EOL + EOL + second + EOL + EOL + first + EOL + '$'));
286 }
287});
288
289test('should reject if conventional bump passes error', async () => {
290 setup();
291 const options = getOptions({ preset: 'what?' });
292 await assert.rejects(
293 runTasks(...options),
294 'Error: Unable to load the "what?" preset package. Please make sure it\'s installed.'
295 );
296});
297
298test('should reject if conventional changelog has error', async () => {
299 setup();
300 const options = getOptions({ preset: () => {} });
301 await assert.rejects(runTasks(...options), /preset must be string or object with property `name`/i);
302});
303
304test('should not write infile in dry run', async () => {
305 const { dir } = setup();
306 const infile = path.join(dir, 'DRYRUN.md');
307 const [config, container] = getOptions({ preset, infile });
308 config['dry-run'] = true;
309 await runTasks(config, container);
310 assert.throws(() => fs.readFileSync(infile), /no such file/);
311});
312
313test('should not write infile if set to false', async () => {
314 const { dir } = setup();
315 const infile = path.join(dir, 'DRYRUN.md');
316 const options = getOptions({ preset, infile: false });
317 const { version } = await runTasks(...options);
318 assert.throws(() => fs.readFileSync(infile), /no such file/);
319 assert.equal(version, '0.0.1');
320});
321
322test('should not bump when recommended bump returns null', async () => {
323 setup();
324 sh.exec(`git tag 1.0.0`);
325 add('fix', 'bar');
326 add('feat', 'baz');
327 {
328 const options = getOptions({ preset: 'angular' });
329 const { version } = await runTasks(...options);
330 assert.equal(version, '1.1.0');
331 }
332 add('blorp', 'faz');
333 add('faz', 'blorp');
334 {
335 const options = getOptions({ preset: 'angular' });
336 const { version } = await runTasks(...options);
337 assert.equal(version, '1.1.0'); // Incorrect result from conventional-recommended-bump
338 }
339 {
340 const whatBump = commits => ({ level: null, reason: 'Parsed commits do not warrant a version bump.' });
341 const options = getOptions({ whatBump });
342 const { version } = await runTasks(...options);
343 assert.equal(version, undefined);
344 }
345});
346
347// TODO Prepare test and verify results influenced by parserOpts and writerOpts
348test.skip('should pass parserOpts and writerOpts', async t => {
349 setup();
350 const parserOpts = {
351 mergePattern: /^Merge pull request #(\d+) from (.*)$/,
352 mergeCorrespondence: ['id', 'source']
353 };
354 const writerOpts = {
355 groupBy: 'type'
356 };
357 const [config, container] = getOptions({ preset, parserOpts, writerOpts });
358 await runTasks(config, container);
359});