@next/codemod 16.2.0-canary.23 → 16.2.0-canary.25
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/lib/__tests__/agents-md-e2e.test.js +294 -0
- package/lib/agents-md.js +18 -8
- package/package.json +1 -1
- package/lib/__tests__/agents-md.test.js +0 -142
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/* global jest */
|
|
2
|
+
jest.autoMockOff()
|
|
3
|
+
|
|
4
|
+
const fs = require('fs')
|
|
5
|
+
const path = require('path')
|
|
6
|
+
const os = require('os')
|
|
7
|
+
const { runAgentsMd } = require('../../bin/agents-md')
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* TRUE E2E TESTS
|
|
11
|
+
* These tests invoke the actual CLI entry point (runAgentsMd),
|
|
12
|
+
* simulating what happens when a user runs:
|
|
13
|
+
* npx @next/codemod agents-md --version 15.0.0 --output CLAUDE.md
|
|
14
|
+
*/
|
|
15
|
+
describe('agents-md e2e (CLI invocation)', () => {
|
|
16
|
+
let testProjectDir
|
|
17
|
+
let originalConsoleLog
|
|
18
|
+
let consoleOutput
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
// Create isolated test project directory
|
|
22
|
+
const tmpBase = process.env.NEXT_TEST_DIR || os.tmpdir()
|
|
23
|
+
testProjectDir = path.join(
|
|
24
|
+
tmpBase,
|
|
25
|
+
`agents-md-e2e-${Date.now()}-${(Math.random() * 1000) | 0}`
|
|
26
|
+
)
|
|
27
|
+
fs.mkdirSync(testProjectDir, { recursive: true })
|
|
28
|
+
|
|
29
|
+
// Mock console.log to capture CLI output
|
|
30
|
+
originalConsoleLog = console.log
|
|
31
|
+
consoleOutput = []
|
|
32
|
+
console.log = (...args) => {
|
|
33
|
+
consoleOutput.push(args.join(' '))
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
// Restore console.log
|
|
39
|
+
console.log = originalConsoleLog
|
|
40
|
+
|
|
41
|
+
// Clean up test directory
|
|
42
|
+
if (testProjectDir && fs.existsSync(testProjectDir)) {
|
|
43
|
+
fs.rmSync(testProjectDir, { recursive: true, force: true })
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('creates CLAUDE.md and .next-docs directory when run with --version and --output', async () => {
|
|
48
|
+
// Create a minimal package.json (not required, but realistic)
|
|
49
|
+
const packageJson = {
|
|
50
|
+
name: 'test-project',
|
|
51
|
+
version: '1.0.0',
|
|
52
|
+
dependencies: {
|
|
53
|
+
next: '15.0.0',
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
fs.writeFileSync(
|
|
57
|
+
path.join(testProjectDir, 'package.json'),
|
|
58
|
+
JSON.stringify(packageJson, null, 2)
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
// Change to test directory
|
|
62
|
+
const originalCwd = process.cwd()
|
|
63
|
+
process.chdir(testProjectDir)
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
// Run the actual CLI command
|
|
67
|
+
await runAgentsMd({
|
|
68
|
+
version: '15.0.0',
|
|
69
|
+
output: 'CLAUDE.md',
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
// Verify .next-docs directory was created and populated
|
|
73
|
+
const docsDir = path.join(testProjectDir, '.next-docs')
|
|
74
|
+
expect(fs.existsSync(docsDir)).toBe(true)
|
|
75
|
+
|
|
76
|
+
const docFiles = fs.readdirSync(docsDir, { recursive: true })
|
|
77
|
+
expect(docFiles.length).toBeGreaterThan(0)
|
|
78
|
+
|
|
79
|
+
// Should contain mdx/md files
|
|
80
|
+
const mdxFiles = docFiles.filter(
|
|
81
|
+
(f) => f.endsWith('.mdx') || f.endsWith('.md')
|
|
82
|
+
)
|
|
83
|
+
expect(mdxFiles.length).toBeGreaterThan(0)
|
|
84
|
+
|
|
85
|
+
// Verify CLAUDE.md was created
|
|
86
|
+
const claudeMdPath = path.join(testProjectDir, 'CLAUDE.md')
|
|
87
|
+
expect(fs.existsSync(claudeMdPath)).toBe(true)
|
|
88
|
+
|
|
89
|
+
const claudeMdContent = fs.readFileSync(claudeMdPath, 'utf-8')
|
|
90
|
+
|
|
91
|
+
// Verify content structure
|
|
92
|
+
expect(claudeMdContent).toContain('<!-- NEXT-AGENTS-MD-START -->')
|
|
93
|
+
expect(claudeMdContent).toContain('<!-- NEXT-AGENTS-MD-END -->')
|
|
94
|
+
expect(claudeMdContent).toContain('[Next.js Docs Index]')
|
|
95
|
+
expect(claudeMdContent).toContain('root: ./.next-docs')
|
|
96
|
+
|
|
97
|
+
// Verify paths are normalized to forward slashes (cross-platform)
|
|
98
|
+
const lines = claudeMdContent.split('|')
|
|
99
|
+
const pathLines = lines.filter((line) => line.includes(':'))
|
|
100
|
+
pathLines.forEach((line) => {
|
|
101
|
+
// Should not contain Windows backslashes in the output
|
|
102
|
+
const pathPart = line.split(':')[0]
|
|
103
|
+
if (pathPart && pathPart.includes('/')) {
|
|
104
|
+
expect(line).not.toMatch(/[^:]\\/)
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
// Verify .gitignore was updated
|
|
109
|
+
const gitignorePath = path.join(testProjectDir, '.gitignore')
|
|
110
|
+
if (fs.existsSync(gitignorePath)) {
|
|
111
|
+
const gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8')
|
|
112
|
+
expect(gitignoreContent).toContain('.next-docs')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Verify console output
|
|
116
|
+
const output = consoleOutput.join('\n')
|
|
117
|
+
expect(output).toContain('Downloading Next.js')
|
|
118
|
+
expect(output).toContain('15.0.0')
|
|
119
|
+
expect(output).toContain('CLAUDE.md')
|
|
120
|
+
} finally {
|
|
121
|
+
// Restore original directory
|
|
122
|
+
process.chdir(originalCwd)
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('updates existing CLAUDE.md without losing content', async () => {
|
|
127
|
+
const originalCwd = process.cwd()
|
|
128
|
+
process.chdir(testProjectDir)
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
// Create existing CLAUDE.md with custom content
|
|
132
|
+
const existingContent = `# My Project
|
|
133
|
+
|
|
134
|
+
This is my project documentation.
|
|
135
|
+
|
|
136
|
+
## Features
|
|
137
|
+
- Feature 1
|
|
138
|
+
- Feature 2
|
|
139
|
+
`
|
|
140
|
+
fs.writeFileSync(
|
|
141
|
+
path.join(testProjectDir, 'CLAUDE.md'),
|
|
142
|
+
existingContent
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
// Run CLI
|
|
146
|
+
await runAgentsMd({
|
|
147
|
+
version: '15.0.0',
|
|
148
|
+
output: 'CLAUDE.md',
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
// Verify file was updated, not replaced
|
|
152
|
+
const claudeMdContent = fs.readFileSync(
|
|
153
|
+
path.join(testProjectDir, 'CLAUDE.md'),
|
|
154
|
+
'utf-8'
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
// Original content should still be there
|
|
158
|
+
expect(claudeMdContent).toContain('# My Project')
|
|
159
|
+
expect(claudeMdContent).toContain('This is my project documentation.')
|
|
160
|
+
expect(claudeMdContent).toContain('## Features')
|
|
161
|
+
|
|
162
|
+
// New index should be injected
|
|
163
|
+
expect(claudeMdContent).toContain('<!-- NEXT-AGENTS-MD-START -->')
|
|
164
|
+
expect(claudeMdContent).toContain('[Next.js Docs Index]')
|
|
165
|
+
} finally {
|
|
166
|
+
process.chdir(originalCwd)
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it('handles custom output filename', async () => {
|
|
171
|
+
const originalCwd = process.cwd()
|
|
172
|
+
process.chdir(testProjectDir)
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
// Run with custom output file
|
|
176
|
+
await runAgentsMd({
|
|
177
|
+
version: '15.0.0',
|
|
178
|
+
output: 'AGENTS.md',
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
// Verify AGENTS.md was created (not CLAUDE.md)
|
|
182
|
+
expect(fs.existsSync(path.join(testProjectDir, 'AGENTS.md'))).toBe(true)
|
|
183
|
+
expect(fs.existsSync(path.join(testProjectDir, 'CLAUDE.md'))).toBe(false)
|
|
184
|
+
|
|
185
|
+
const agentsMdContent = fs.readFileSync(
|
|
186
|
+
path.join(testProjectDir, 'AGENTS.md'),
|
|
187
|
+
'utf-8'
|
|
188
|
+
)
|
|
189
|
+
expect(agentsMdContent).toContain('[Next.js Docs Index]')
|
|
190
|
+
} finally {
|
|
191
|
+
process.chdir(originalCwd)
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
it('works when run from a subdirectory', async () => {
|
|
196
|
+
const originalCwd = process.cwd()
|
|
197
|
+
|
|
198
|
+
// Create a subdirectory
|
|
199
|
+
const subDir = path.join(testProjectDir, 'packages', 'app')
|
|
200
|
+
fs.mkdirSync(subDir, { recursive: true })
|
|
201
|
+
|
|
202
|
+
// Create package.json in root
|
|
203
|
+
const packageJson = {
|
|
204
|
+
dependencies: { next: '15.0.0' },
|
|
205
|
+
}
|
|
206
|
+
fs.writeFileSync(
|
|
207
|
+
path.join(testProjectDir, 'package.json'),
|
|
208
|
+
JSON.stringify(packageJson)
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
// Change to subdirectory
|
|
212
|
+
process.chdir(subDir)
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
// Run from subdirectory - should create files in CWD (subdirectory)
|
|
216
|
+
await runAgentsMd({
|
|
217
|
+
version: '15.0.0',
|
|
218
|
+
output: 'CLAUDE.md',
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
// Verify files created in subdirectory
|
|
222
|
+
expect(fs.existsSync(path.join(subDir, 'CLAUDE.md'))).toBe(true)
|
|
223
|
+
expect(fs.existsSync(path.join(subDir, '.next-docs'))).toBe(true)
|
|
224
|
+
} finally {
|
|
225
|
+
process.chdir(originalCwd)
|
|
226
|
+
}
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
it('normalizes paths on Windows (cross-platform test)', async () => {
|
|
230
|
+
const originalCwd = process.cwd()
|
|
231
|
+
process.chdir(testProjectDir)
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
await runAgentsMd({
|
|
235
|
+
version: '15.0.0',
|
|
236
|
+
output: 'CLAUDE.md',
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
const claudeMdContent = fs.readFileSync(
|
|
240
|
+
path.join(testProjectDir, 'CLAUDE.md'),
|
|
241
|
+
'utf-8'
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
// Extract the index content between markers
|
|
245
|
+
const startMarker = '<!-- NEXT-AGENTS-MD-START -->'
|
|
246
|
+
const endMarker = '<!-- NEXT-AGENTS-MD-END -->'
|
|
247
|
+
const startIdx = claudeMdContent.indexOf(startMarker) + startMarker.length
|
|
248
|
+
const endIdx = claudeMdContent.indexOf(endMarker)
|
|
249
|
+
const indexContent = claudeMdContent.slice(startIdx, endIdx)
|
|
250
|
+
|
|
251
|
+
// Parse the index (format: "dir:{file1,file2}|dir2:{file3}")
|
|
252
|
+
const sections = indexContent.split('|').filter((s) => s.includes(':'))
|
|
253
|
+
|
|
254
|
+
sections.forEach((section) => {
|
|
255
|
+
const [dirPath, filesStr] = section.split(':')
|
|
256
|
+
if (dirPath && dirPath.trim() && !dirPath.includes('root')) {
|
|
257
|
+
// Verify no Windows backslashes in directory paths
|
|
258
|
+
expect(dirPath).not.toContain('\\')
|
|
259
|
+
// Verify uses forward slashes
|
|
260
|
+
if (dirPath.includes('/')) {
|
|
261
|
+
expect(dirPath).toMatch(/^[^\\]+$/)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
})
|
|
265
|
+
} finally {
|
|
266
|
+
process.chdir(originalCwd)
|
|
267
|
+
}
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
it('handles version that requires git clone from GitHub', async () => {
|
|
271
|
+
const originalCwd = process.cwd()
|
|
272
|
+
process.chdir(testProjectDir)
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
// Use a known stable version
|
|
276
|
+
await runAgentsMd({
|
|
277
|
+
version: '14.2.0',
|
|
278
|
+
output: 'CLAUDE.md',
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
// Verify docs were downloaded
|
|
282
|
+
const docsDir = path.join(testProjectDir, '.next-docs')
|
|
283
|
+
expect(fs.existsSync(docsDir)).toBe(true)
|
|
284
|
+
|
|
285
|
+
const docFiles = fs.readdirSync(docsDir, { recursive: true })
|
|
286
|
+
const mdxFiles = docFiles.filter(
|
|
287
|
+
(f) => f.endsWith('.mdx') || f.endsWith('.md')
|
|
288
|
+
)
|
|
289
|
+
expect(mdxFiles.length).toBeGreaterThan(50) // Should have many doc files
|
|
290
|
+
} finally {
|
|
291
|
+
process.chdir(originalCwd)
|
|
292
|
+
}
|
|
293
|
+
}, 30000) // Increase timeout for git clone
|
|
294
|
+
})
|
package/lib/agents-md.js
CHANGED
|
@@ -16,7 +16,7 @@ exports.buildDocTree = buildDocTree;
|
|
|
16
16
|
exports.generateClaudeMdIndex = generateClaudeMdIndex;
|
|
17
17
|
exports.injectIntoClaudeMd = injectIntoClaudeMd;
|
|
18
18
|
exports.ensureGitignoreEntry = ensureGitignoreEntry;
|
|
19
|
-
const
|
|
19
|
+
const execa_1 = __importDefault(require("execa"));
|
|
20
20
|
const fs_1 = __importDefault(require("fs"));
|
|
21
21
|
const path_1 = __importDefault(require("path"));
|
|
22
22
|
const os_1 = __importDefault(require("os"));
|
|
@@ -108,7 +108,17 @@ async function cloneDocsFolder(tag, destDir) {
|
|
|
108
108
|
const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'next-agents-md-'));
|
|
109
109
|
try {
|
|
110
110
|
try {
|
|
111
|
-
(0,
|
|
111
|
+
await (0, execa_1.default)('git', [
|
|
112
|
+
'clone',
|
|
113
|
+
'--depth',
|
|
114
|
+
'1',
|
|
115
|
+
'--filter=blob:none',
|
|
116
|
+
'--sparse',
|
|
117
|
+
'--branch',
|
|
118
|
+
tag,
|
|
119
|
+
'https://github.com/vercel/next.js.git',
|
|
120
|
+
'.',
|
|
121
|
+
], { cwd: tempDir });
|
|
112
122
|
}
|
|
113
123
|
catch (error) {
|
|
114
124
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -117,7 +127,7 @@ async function cloneDocsFolder(tag, destDir) {
|
|
|
117
127
|
}
|
|
118
128
|
throw error;
|
|
119
129
|
}
|
|
120
|
-
(0,
|
|
130
|
+
await (0, execa_1.default)('git', ['sparse-checkout', 'set', 'docs'], { cwd: tempDir });
|
|
121
131
|
const sourceDocsDir = path_1.default.join(tempDir, 'docs');
|
|
122
132
|
if (!fs_1.default.existsSync(sourceDocsDir)) {
|
|
123
133
|
throw new Error('docs folder not found in cloned repository');
|
|
@@ -137,16 +147,16 @@ async function cloneDocsFolder(tag, destDir) {
|
|
|
137
147
|
function collectDocFiles(dir) {
|
|
138
148
|
return fs_1.default.readdirSync(dir, { recursive: true })
|
|
139
149
|
.filter((f) => (f.endsWith('.mdx') || f.endsWith('.md')) &&
|
|
140
|
-
|
|
141
|
-
|
|
150
|
+
!/[/\\]index\.mdx$/.test(f) &&
|
|
151
|
+
!/[/\\]index\.md$/.test(f) &&
|
|
142
152
|
!f.startsWith('index.'))
|
|
143
153
|
.sort()
|
|
144
|
-
.map((f) => ({ relativePath: f }));
|
|
154
|
+
.map((f) => ({ relativePath: f.replace(/\\/g, '/') }));
|
|
145
155
|
}
|
|
146
156
|
function buildDocTree(files) {
|
|
147
157
|
const sections = new Map();
|
|
148
158
|
for (const file of files) {
|
|
149
|
-
const parts = file.relativePath.split(
|
|
159
|
+
const parts = file.relativePath.split(/[/\\]/);
|
|
150
160
|
if (parts.length < 2)
|
|
151
161
|
continue;
|
|
152
162
|
const topLevelDir = parts[0];
|
|
@@ -221,7 +231,7 @@ function collectAllFilesFromSections(sections) {
|
|
|
221
231
|
function groupByDirectory(files) {
|
|
222
232
|
const grouped = new Map();
|
|
223
233
|
for (const filePath of files) {
|
|
224
|
-
const lastSlash = filePath.lastIndexOf('/');
|
|
234
|
+
const lastSlash = Math.max(filePath.lastIndexOf('/'), filePath.lastIndexOf('\\'));
|
|
225
235
|
const dir = lastSlash === -1 ? '.' : filePath.slice(0, lastSlash);
|
|
226
236
|
const fileName = lastSlash === -1 ? filePath : filePath.slice(lastSlash + 1);
|
|
227
237
|
const existing = grouped.get(dir);
|
package/package.json
CHANGED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
/* global jest */
|
|
2
|
-
jest.autoMockOff()
|
|
3
|
-
|
|
4
|
-
const { injectIntoClaudeMd, buildDocTree } = require('../agents-md')
|
|
5
|
-
|
|
6
|
-
describe('agents-md', () => {
|
|
7
|
-
describe('injectIntoClaudeMd', () => {
|
|
8
|
-
const START_MARKER = '<!-- NEXT-AGENTS-MD-START -->'
|
|
9
|
-
const END_MARKER = '<!-- NEXT-AGENTS-MD-END -->'
|
|
10
|
-
|
|
11
|
-
it('appends to empty file', () => {
|
|
12
|
-
const result = injectIntoClaudeMd('', 'index content')
|
|
13
|
-
// Empty string doesn't end with \n, so separator is \n\n
|
|
14
|
-
expect(result).toBe(`\n\n${START_MARKER}index content${END_MARKER}\n`)
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
it('appends to file without markers', () => {
|
|
18
|
-
const existing = '# My Project\n\nSome existing content.'
|
|
19
|
-
const result = injectIntoClaudeMd(existing, 'index content')
|
|
20
|
-
expect(result).toBe(
|
|
21
|
-
`${existing}\n\n${START_MARKER}index content${END_MARKER}\n`
|
|
22
|
-
)
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
it('replaces content between existing markers', () => {
|
|
26
|
-
const existing = `# My Project
|
|
27
|
-
|
|
28
|
-
Some content before.
|
|
29
|
-
|
|
30
|
-
${START_MARKER}old index${END_MARKER}
|
|
31
|
-
|
|
32
|
-
Some content after.`
|
|
33
|
-
const result = injectIntoClaudeMd(existing, 'new index')
|
|
34
|
-
expect(result).toBe(`# My Project
|
|
35
|
-
|
|
36
|
-
Some content before.
|
|
37
|
-
|
|
38
|
-
${START_MARKER}new index${END_MARKER}
|
|
39
|
-
|
|
40
|
-
Some content after.`)
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('is idempotent - running twice produces same result', () => {
|
|
44
|
-
const initial = '# Project\n'
|
|
45
|
-
const first = injectIntoClaudeMd(initial, 'index v1')
|
|
46
|
-
const second = injectIntoClaudeMd(first, 'index v1')
|
|
47
|
-
expect(second).toBe(first)
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
it('preserves content before and after markers on update', () => {
|
|
51
|
-
const before = '# Header\n\nIntro paragraph.'
|
|
52
|
-
const after = '\n\n## Footer\n\nMore content.'
|
|
53
|
-
const existing = `${before}\n\n${START_MARKER}old${END_MARKER}${after}`
|
|
54
|
-
const result = injectIntoClaudeMd(existing, 'new')
|
|
55
|
-
expect(result).toContain(before)
|
|
56
|
-
expect(result).toContain(after)
|
|
57
|
-
expect(result).toContain(`${START_MARKER}new${END_MARKER}`)
|
|
58
|
-
expect(result).not.toContain('old')
|
|
59
|
-
})
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
describe('buildDocTree', () => {
|
|
63
|
-
it('groups files by top-level directory', () => {
|
|
64
|
-
const files = [
|
|
65
|
-
{ relativePath: '01-getting-started/installation.mdx' },
|
|
66
|
-
{ relativePath: '01-getting-started/project-structure.mdx' },
|
|
67
|
-
{ relativePath: '02-app/routing.mdx' },
|
|
68
|
-
]
|
|
69
|
-
const tree = buildDocTree(files)
|
|
70
|
-
|
|
71
|
-
expect(tree).toHaveLength(2)
|
|
72
|
-
expect(tree[0].name).toBe('01-getting-started')
|
|
73
|
-
expect(tree[0].files).toHaveLength(2)
|
|
74
|
-
expect(tree[1].name).toBe('02-app')
|
|
75
|
-
expect(tree[1].files).toHaveLength(1)
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
it('creates nested subsections for deeper paths', () => {
|
|
79
|
-
const files = [
|
|
80
|
-
{ relativePath: '02-app/01-building/layouts.mdx' },
|
|
81
|
-
{ relativePath: '02-app/01-building/pages.mdx' },
|
|
82
|
-
{ relativePath: '02-app/02-api/route-handlers.mdx' },
|
|
83
|
-
]
|
|
84
|
-
const tree = buildDocTree(files)
|
|
85
|
-
|
|
86
|
-
expect(tree).toHaveLength(1)
|
|
87
|
-
const appSection = tree[0]
|
|
88
|
-
expect(appSection.name).toBe('02-app')
|
|
89
|
-
expect(appSection.files).toHaveLength(0) // no direct files
|
|
90
|
-
expect(appSection.subsections).toHaveLength(2)
|
|
91
|
-
|
|
92
|
-
const building = appSection.subsections.find(
|
|
93
|
-
(s) => s.name === '01-building'
|
|
94
|
-
)
|
|
95
|
-
expect(building).toBeDefined()
|
|
96
|
-
expect(building.files).toHaveLength(2)
|
|
97
|
-
|
|
98
|
-
const api = appSection.subsections.find((s) => s.name === '02-api')
|
|
99
|
-
expect(api).toBeDefined()
|
|
100
|
-
expect(api.files).toHaveLength(1)
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
it('handles 4-level deep paths with sub-subsections', () => {
|
|
104
|
-
const files = [
|
|
105
|
-
{ relativePath: '02-app/01-building/01-routing/dynamic-routes.mdx' },
|
|
106
|
-
{ relativePath: '02-app/01-building/01-routing/parallel-routes.mdx' },
|
|
107
|
-
]
|
|
108
|
-
const tree = buildDocTree(files)
|
|
109
|
-
|
|
110
|
-
const routing = tree[0].subsections[0].subsections[0]
|
|
111
|
-
expect(routing.name).toBe('01-routing')
|
|
112
|
-
expect(routing.files).toHaveLength(2)
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
it('skips single-segment paths (root-level files)', () => {
|
|
116
|
-
const files = [
|
|
117
|
-
{ relativePath: 'index.mdx' },
|
|
118
|
-
{ relativePath: '01-getting-started/intro.mdx' },
|
|
119
|
-
]
|
|
120
|
-
const tree = buildDocTree(files)
|
|
121
|
-
|
|
122
|
-
// Root-level index.mdx should be skipped (parts.length < 2)
|
|
123
|
-
expect(tree).toHaveLength(1)
|
|
124
|
-
expect(tree[0].name).toBe('01-getting-started')
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
it('sorts sections and files alphabetically', () => {
|
|
128
|
-
const files = [
|
|
129
|
-
{ relativePath: 'z-section/b-file.mdx' },
|
|
130
|
-
{ relativePath: 'a-section/z-file.mdx' },
|
|
131
|
-
{ relativePath: 'a-section/a-file.mdx' },
|
|
132
|
-
{ relativePath: 'z-section/a-file.mdx' },
|
|
133
|
-
]
|
|
134
|
-
const tree = buildDocTree(files)
|
|
135
|
-
|
|
136
|
-
expect(tree[0].name).toBe('a-section')
|
|
137
|
-
expect(tree[1].name).toBe('z-section')
|
|
138
|
-
expect(tree[0].files[0].relativePath).toBe('a-section/a-file.mdx')
|
|
139
|
-
expect(tree[0].files[1].relativePath).toBe('a-section/z-file.mdx')
|
|
140
|
-
})
|
|
141
|
-
})
|
|
142
|
-
})
|