Skip to content

Commit f5ebe61

Browse files
test(changelog): add changelog generator and pages tests
主要变更: - 新增 changelog 生成器的单元测试 - 新增 changelog 页面的集成测试 - 覆盖 sidebar 配置、页面结构和组件功能 Co-Authored-By: Hagicode <noreply@hagicode.com>
1 parent d24b581 commit f5ebe61

2 files changed

Lines changed: 250 additions & 0 deletions

File tree

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import test from 'node:test';
2+
import assert from 'node:assert/strict';
3+
import { execFileSync } from 'node:child_process';
4+
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
5+
import os from 'node:os';
6+
import path from 'node:path';
7+
8+
import {
9+
buildReleaseWindows,
10+
buildRepositoryChangelog,
11+
collectTagWindowCommits,
12+
listRepositoryTags,
13+
} from '../scripts/generate-multi-repo-changelog.mjs';
14+
15+
function git(repoDir, args, env = {}) {
16+
return execFileSync('git', ['-C', repoDir, ...args], {
17+
encoding: 'utf8',
18+
env: {
19+
...process.env,
20+
...env,
21+
},
22+
});
23+
}
24+
25+
async function createTempRepository(t) {
26+
const repoDir = await mkdtemp(path.join(os.tmpdir(), 'docs-changelog-'));
27+
t.after(async () => {
28+
await rm(repoDir, { recursive: true, force: true });
29+
});
30+
31+
git(repoDir, ['init', '-b', 'main']);
32+
git(repoDir, ['config', 'user.name', 'Docs Test']);
33+
git(repoDir, ['config', 'user.email', 'docs-test@example.com']);
34+
35+
return repoDir;
36+
}
37+
38+
async function commitFile(repoDir, filename, content, date, subject, body = '') {
39+
await writeFile(path.join(repoDir, filename), content, 'utf8');
40+
git(repoDir, ['add', filename]);
41+
42+
const args = ['commit', '-m', subject];
43+
if (body.length > 0) {
44+
args.push('-m', body);
45+
}
46+
47+
git(repoDir, args, {
48+
GIT_AUTHOR_DATE: date,
49+
GIT_COMMITTER_DATE: date,
50+
});
51+
}
52+
53+
test('generator builds adjacent tag windows and skips commits before the earliest tag', async (t) => {
54+
const repoDir = await createTempRepository(t);
55+
56+
await commitFile(repoDir, 'notes.md', '# init\n', '2026-01-01T00:00:00Z', 'chore: init');
57+
git(repoDir, ['tag', '0.1.0']);
58+
59+
await commitFile(
60+
repoDir,
61+
'notes.md',
62+
'# init\n\nsecond\n',
63+
'2026-01-02T00:00:00Z',
64+
'feat: add second change',
65+
'detail for second change',
66+
);
67+
await commitFile(
68+
repoDir,
69+
'notes.md',
70+
'# init\n\nsecond\n\nthird\n',
71+
'2026-01-03T00:00:00Z',
72+
'fix: prepare release two',
73+
'detail for release two',
74+
);
75+
git(repoDir, ['tag', 'v0.2.0']);
76+
77+
await commitFile(
78+
repoDir,
79+
'notes.md',
80+
'# init\n\nsecond\n\nthird\n\nfourth\n',
81+
'2026-01-04T00:00:00Z',
82+
'docs: release three',
83+
);
84+
git(repoDir, ['tag', 'V0.3.0']);
85+
86+
const tags = listRepositoryTags(repoDir);
87+
assert.deepEqual(
88+
tags.map((tag) => tag.name),
89+
['0.1.0', 'v0.2.0', 'V0.3.0'],
90+
);
91+
92+
const windows = buildReleaseWindows(tags);
93+
assert.deepEqual(
94+
windows.map((window) => [window.previousTag.name, window.currentTag.name]),
95+
[
96+
['0.1.0', 'v0.2.0'],
97+
['v0.2.0', 'V0.3.0'],
98+
],
99+
);
100+
101+
const secondReleaseCommits = collectTagWindowCommits(repoDir, '0.1.0', 'v0.2.0');
102+
assert.equal(secondReleaseCommits.length, 2);
103+
assert.deepEqual(
104+
secondReleaseCommits.map((commit) => commit.title),
105+
['feat: add second change', 'fix: prepare release two'],
106+
);
107+
assert.deepEqual(
108+
secondReleaseCommits.map((commit) => commit.detail),
109+
['detail for second change', 'detail for release two'],
110+
);
111+
112+
const changelog = buildRepositoryChangelog(
113+
{
114+
repoKey: 'fixture',
115+
repoPath: repoDir,
116+
outputPath: 'unused.json',
117+
},
118+
{
119+
monorepoRoot: '/',
120+
},
121+
);
122+
123+
assert.equal(changelog.releases.length, 2);
124+
assert.deepEqual(
125+
changelog.releases.map((release) => release.tag),
126+
['V0.3.0', 'v0.2.0'],
127+
);
128+
assert.equal(changelog.releases[0].commitCount, 1);
129+
assert.equal(changelog.releases[1].commitCount, 2);
130+
assert.equal(changelog.releases[0].commits[0].title, 'docs: release three');
131+
assert.equal(changelog.releases[1].commits[0].title, 'feat: add second change');
132+
});
133+
134+
test('generator returns a valid empty release list for repositories with a single tag', async (t) => {
135+
const repoDir = await createTempRepository(t);
136+
137+
await commitFile(repoDir, 'README.md', 'single tag repo\n', '2026-02-01T00:00:00Z', 'feat: first release');
138+
git(repoDir, ['tag', 'v1.0.0']);
139+
140+
const changelog = buildRepositoryChangelog(
141+
{
142+
repoKey: 'single',
143+
repoPath: repoDir,
144+
outputPath: 'unused.json',
145+
},
146+
{
147+
monorepoRoot: '/',
148+
},
149+
);
150+
151+
assert.equal(changelog.repoKey, 'single');
152+
assert.equal(changelog.releases.length, 0);
153+
});
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import test from 'node:test';
2+
import assert from 'node:assert/strict';
3+
import { access, readFile } from 'node:fs/promises';
4+
import path from 'node:path';
5+
import { fileURLToPath } from 'node:url';
6+
7+
const docsRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
8+
9+
test('docs sidebar config exposes the localized changelog group', async () => {
10+
const astroConfig = await readFile(path.join(docsRoot, 'astro.config.mjs'), 'utf8');
11+
12+
assert.match(astroConfig, /label:\s*""/);
13+
assert.match(astroConfig, /translations:\s*\{\s*en:\s*"Changelog"\s*\}/);
14+
assert.match(astroConfig, /autogenerate:\s*\{\s*directory:\s*"changelog"\s*\}/);
15+
});
16+
17+
test('changelog pages exist for core, web, and desktop in both locales', async () => {
18+
const pageExpectations = [
19+
{
20+
filePath: path.join(docsRoot, 'src', 'content', 'docs', 'changelog', 'core.mdx'),
21+
repoLabel: 'Core',
22+
locale: 'zh-CN',
23+
jsonFile: 'core.json',
24+
},
25+
{
26+
filePath: path.join(docsRoot, 'src', 'content', 'docs', 'changelog', 'web.mdx'),
27+
repoLabel: 'Web',
28+
locale: 'zh-CN',
29+
jsonFile: 'web.json',
30+
},
31+
{
32+
filePath: path.join(docsRoot, 'src', 'content', 'docs', 'changelog', 'desktop.mdx'),
33+
repoLabel: 'Desktop',
34+
locale: 'zh-CN',
35+
jsonFile: 'desktop.json',
36+
},
37+
{
38+
filePath: path.join(docsRoot, 'src', 'content', 'docs', 'en', 'changelog', 'core.mdx'),
39+
repoLabel: 'Core',
40+
locale: 'en',
41+
jsonFile: 'core.json',
42+
},
43+
{
44+
filePath: path.join(docsRoot, 'src', 'content', 'docs', 'en', 'changelog', 'web.mdx'),
45+
repoLabel: 'Web',
46+
locale: 'en',
47+
jsonFile: 'web.json',
48+
},
49+
{
50+
filePath: path.join(docsRoot, 'src', 'content', 'docs', 'en', 'changelog', 'desktop.mdx'),
51+
repoLabel: 'Desktop',
52+
locale: 'en',
53+
jsonFile: 'desktop.json',
54+
},
55+
];
56+
57+
for (const page of pageExpectations) {
58+
await access(page.filePath);
59+
const content = await readFile(page.filePath, 'utf8');
60+
61+
assert.match(content, /RepoChangelogPage/);
62+
assert.match(content, new RegExp(`repoLabel="${page.repoLabel}"`));
63+
assert.match(content, new RegExp(`locale="${page.locale}"`));
64+
assert.match(content, new RegExp(page.jsonFile.replace('.', '\\.')));
65+
}
66+
});
67+
68+
test('shared changelog component keeps grouped release markup and explicit empty-state copy', async () => {
69+
const componentPath = path.join(docsRoot, 'src', 'components', 'RepoChangelogPage.astro');
70+
const component = await readFile(componentPath, 'utf8');
71+
72+
assert.match(component, /function getCanonicalReleaseAnchor/);
73+
assert.match(component, /replace\(\s*\/\^#\/,\s*''\s*\)/);
74+
assert.match(component, /replace\(\s*\/\^v\+\/,\s*''\s*\)/);
75+
assert.match(component, /toLowerCase\(\)/);
76+
assert.match(component, /data-repo-changelog/);
77+
assert.match(component, /data-release-filter-select/);
78+
assert.match(component, /searchParams\.get\('tag'\)/);
79+
assert.match(component, /location\.hash/);
80+
assert.match(component, /data-release-anchor-value/);
81+
assert.match(component, /findCardByHash/);
82+
assert.match(component, /scrollIntoView\(\{ block: 'start', behavior: 'auto' \}\)/);
83+
assert.match(component, /focus\(\{ preventScroll: true \}\)/);
84+
assert.match(component, /data-release-tag-value/);
85+
assert.match(component, /data-release-block/);
86+
assert.match(component, /data-commit-item/);
87+
assert.match(component, /data-changelog-empty/);
88+
assert.match(component, /:global\(html\[data-theme='dark'\]\) \.repo-changelog/);
89+
assert.match(component, /--repo-surface/);
90+
assert.match(component, /scroll-margin-top: clamp\(5\.5rem, 14vh, 8rem\)/);
91+
assert.match(component, /No adjacent-tag releases yet/);
92+
assert.match(component, /No commits were collected for this tag window/);
93+
assert.match(component, / tag/);
94+
assert.match(component, / Tag /);
95+
assert.match(component, /release\.tag/);
96+
assert.match(component, /commit\.title/);
97+
});

0 commit comments

Comments
 (0)