test/release.test.js
Rama main · réplica de lectura del repositorio original.
import { describe, it, expect } from 'vitest';
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';
import { parseFragment, nextVersion, changelogWith, release } from '../scripts/release.js';
const frag = (bump, body) => `---\nbump: ${bump}\n---\n${body}\n`;
describe('nextVersion', () => {
it('bumps one part and zeroes the parts after it', () => {
expect(nextVersion('0.6.6.3', ['micro'])).toBe('0.6.6.4');
expect(nextVersion('0.6.6.3', ['patch'])).toBe('0.6.7.0');
expect(nextVersion('0.6.6.3', ['minor'])).toBe('0.7.0.0');
expect(nextVersion('0.6.6.3\n', ['major'])).toBe('1.0.0.0');
});
it('takes the largest level when several fragments land together', () => {
expect(nextVersion('0.6.6.0', ['micro', 'patch', 'micro'])).toBe('0.6.7.0');
});
it('rejects a VERSION that is not four-part', () => {
expect(() => nextVersion('0.6.6', ['patch'])).toThrow(/MAJOR.MINOR.PATCH.MICRO/);
});
});
describe('parseFragment', () => {
it('reads the bump and the notes, CRLF too', () => {
expect(parseFragment(frag('micro', '### Fixed\n\n- a').replace(/\n/g, '\r\n')))
.toEqual({ bump: 'micro', body: '### Fixed\n\n- a' });
});
it('rejects a missing or unknown bump and empty notes', () => {
expect(() => parseFragment('### Fixed\n\n- a', 'x.md')).toThrow(/x.md: needs front matter/);
expect(() => parseFragment(frag('huge', '- a'))).toThrow(/front matter/);
expect(() => parseFragment(frag('patch', ''))).toThrow(/no release notes/);
});
});
describe('changelogWith', () => {
it('puts the new section above the newest one', () => {
const out = changelogWith('# Changelog\n\nintro\n\n## [0.1.0.0] - 2026-01-01\n\nold\n', '0.1.1.0', '2026-02-02', ['a', 'b']);
expect(out).toBe('# Changelog\n\nintro\n\n## [0.1.1.0] - 2026-02-02\n\na\n\nb\n\n## [0.1.0.0] - 2026-01-01\n\nold\n');
});
});
// A malformed fragment would only fail the release run after merge; catch it
// in the PR's own test run instead.
describe('changelog.d in this repo', () => {
it('holds only fragments the release script can read', () => {
for (const n of readdirSync('changelog.d').filter((n) => n.endsWith('.md') && n !== 'README.md')) {
expect(() => parseFragment(readFileSync(join('changelog.d', n), 'utf8'), n)).not.toThrow();
}
});
});
describe('release', () => {
function repo() {
const root = mkdtempSync(join(tmpdir(), 'release-'));
mkdirSync(join(root, 'changelog.d'));
writeFileSync(join(root, 'VERSION'), '0.6.6.0\n');
writeFileSync(join(root, 'package.json'), '{\n "name": "x",\n "version": "0.6.6"\n}\n');
writeFileSync(join(root, 'package-lock.json'), JSON.stringify({ version: '0.6.6', packages: { '': { version: '0.6.6' } } }));
writeFileSync(join(root, 'CHANGELOG.md'), '# Changelog\n\n## [0.6.6.0] - 2026-09-25\n\nold\n');
writeFileSync(join(root, 'changelog.d', 'README.md'), 'how to write a fragment');
return root;
}
const read = (root, f) => readFileSync(join(root, f), 'utf8');
it('does nothing without fragments', () => {
const root = repo();
expect(release(root)).toBe(null);
expect(read(root, 'VERSION')).toBe('0.6.6.0\n');
});
it('releases two parallel PRs as one version with both notes, and deletes the fragments', () => {
const root = repo();
writeFileSync(join(root, 'changelog.d', 'branch-a.md'), frag('micro', '### Fixed\n\n- **A.** from branch a'));
writeFileSync(join(root, 'changelog.d', 'branch-b.md'), frag('patch', '### Added\n\n- **B.** from branch b'));
expect(release(root, '2026-09-26')).toBe('0.6.7.0');
expect(read(root, 'VERSION')).toBe('0.6.7.0\n');
expect(JSON.parse(read(root, 'package.json')).version).toBe('0.6.7');
const lock = JSON.parse(read(root, 'package-lock.json'));
expect([lock.version, lock.packages[''].version]).toEqual(['0.6.7', '0.6.7']);
expect(read(root, 'CHANGELOG.md')).toBe(
'# Changelog\n\n## [0.6.7.0] - 2026-09-26\n\n### Fixed\n\n- **A.** from branch a\n\n### Added\n\n- **B.** from branch b\n\n## [0.6.6.0] - 2026-09-25\n\nold\n',
);
expect(readdirSync(join(root, 'changelog.d'))).toEqual(['README.md']);
});
it('writes nothing when a fragment is malformed', () => {
const root = repo();
writeFileSync(join(root, 'changelog.d', 'ok.md'), frag('patch', '- ok'));
writeFileSync(join(root, 'changelog.d', 'bad.md'), '- no front matter');
expect(() => release(root)).toThrow(/changelog.d\/bad.md/);
expect(read(root, 'VERSION')).toBe('0.6.6.0\n');
expect(readdirSync(join(root, 'changelog.d')).sort()).toEqual(['README.md', 'bad.md', 'ok.md']);
});
});