import assert from 'node:assert/strict'; import { test } from 'node:test'; import { groupCommits, renderAndroidStatus, renderNotes, replaceAndroidStatus, STATUS_END, STATUS_START, } from './release.mjs'; const sha = (n) => String(n).repeat(40).slice(0, 40); const repoUrl = 'https://git.example/owner/pipu-music'; test('groups conventional commits by section in a fixed order', () => { const groups = groupCommits([ { sha: sha(1), subject: 'fix(api): handle empty token' }, { sha: sha(2), subject: 'feat: add wave' }, { sha: sha(3), subject: 'ci(release): build arm64 only' }, { sha: sha(4), subject: 'build(deploy): pin images' }, { sha: sha(5), subject: 'Update README' }, { sha: sha(6), subject: 'chore: bump deps' }, ]); assert.deepEqual( groups.sections.map(([title, items]) => [title, items.map((i) => i.text)]), [ ['Новое', ['add wave']], ['Исправления', ['handle empty token']], ['Сборка и CI', ['build arm64 only', 'pin images']], ['Прочее', ['Update README', 'bump deps']], ], ); assert.equal(groups.sections[1][1][0].scope, 'api'); assert.deepEqual(groups.breaking, []); }); test('marks breaking changes', () => { const groups = groupCommits([{ sha: sha(1), subject: 'feat(api)!: drop v0 endpoints' }]); assert.equal(groups.breaking.length, 1); assert.equal(groups.sections[0][0], 'Новое'); }); test('android status blocks', () => { const building = renderAndroidStatus({ status: 'building', runUrl: 'https://run' }); assert.ok(building.startsWith(STATUS_START) && building.endsWith(STATUS_END)); assert.match(building, /собирается — \[ход сборки\]\(https:\/\/run\)/); const ok = renderAndroidStatus({ status: 'success', versionCode: '10299', signing: 'release', apk: { name: 'pipu-music-0.1.2.apk', size: 25 * 1024 * 1024 + 300_000, sha256: 'ab'.repeat(32) }, }); assert.match(ok, /`pipu-music-0\.1\.2\.apk` \(25,3 МБ\), versionCode `10299`/); assert.doesNotMatch(ok, /отладочным/); const debug = renderAndroidStatus({ status: 'success', signing: 'debug', apk: { name: 'a.apk', size: 1, sha256: 'x' } }); assert.match(debug, /отладочным ключом/); assert.match(renderAndroidStatus({ status: 'failure', runUrl: 'https://run' }), /не удалась — \[лог сборки\]/); assert.match(renderAndroidStatus({ status: 'cancelled', runUrl: 'https://run' }), /отменена — \[лог сборки\]/); }); test('replaces only the status block and is idempotent', () => { const body = `intro\n\n${renderAndroidStatus({ status: 'building', runUrl: 'u' })}\n\n## Что изменилось\n`; const failed = renderAndroidStatus({ status: 'failure', runUrl: 'u' }); const once = replaceAndroidStatus(body, failed); assert.equal(replaceAndroidStatus(once, failed), once); assert.ok(once.startsWith('intro\n\n') && once.endsWith('\n\n## Что изменилось\n')); assert.doesNotMatch(once, /собирается/); assert.equal(once.split(STATUS_START).length, 2); }); test('adds a status block when the body has none', () => { const block = renderAndroidStatus({ status: 'failure', runUrl: 'u' }); assert.ok(replaceAndroidStatus('hand-written notes', block).startsWith(block)); }); test('release notes', () => { const notes = renderNotes({ version: '0.2.0', tag: 'v0.2.0', prevTag: 'v0.1.7', prerelease: false, repoUrl, groups: groupCommits([{ sha: sha(1), subject: 'feat(web): library screen' }]), androidStatus: renderAndroidStatus({ status: 'building', runUrl: 'https://run' }), }); assert.match(notes, /### Новое\n\n- \*\*web:\*\* library screen \(\[1111111\]\(https:\/\/git\.example\/owner\/pipu-music\/commit\/1{40}\)\)/); assert.match(notes, /\[v0\.1\.7\.\.\.v0\.2\.0\]\(https:\/\/git\.example\/owner\/pipu-music\/compare\/v0\.1\.7\.\.\.v0\.2\.0\)/); assert.match(notes, /\.\/update\.sh v0\.2\.0/); assert.match(notes, /"version":"0\.2\.0"/); assert.match(notes, /pipu-music-0\.2\.0\.apk/); assert.match(notes, /src\/tag\/v0\.2\.0\/docs\/install-guide\.md/); assert.doesNotMatch(notes, /Предварительная версия/); }); test('release notes for a first prerelease without changes', () => { const notes = renderNotes({ version: '0.1.0-rc.1', tag: 'v0.1.0-rc.1', prevTag: null, prerelease: true, repoUrl, groups: groupCommits([]), androidStatus: renderAndroidStatus({ status: 'building', runUrl: 'u' }), }); assert.match(notes, /Предварительная версия/); assert.match(notes, /Изменений с прошлой версии нет/); assert.match(notes, /commits\/tag\/v0\.1\.0-rc\.1/); });