-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat: add tests for check markdown script #3378
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5a90f49
5373c9f
a30cef3
a9da240
1079101
86201ed
7c44f4a
c98ecdd
6e34205
4445ce5
333e0b2
8849351
1fbac7c
fada5ab
68cd3ee
998af5a
f5c8964
d92b519
d0fe33a
216a511
8a5235b
a93b45d
2732f0f
4fcdea8
e1a16c1
7401e7f
0fbdc06
02e221b
b17b83e
35d2e84
7921604
9d75691
b36972d
dff6ccc
53c86fd
92cc885
871e305
c0afa97
10c1ba5
2fa09e6
aa0e241
746dc88
4558748
29cd78c
e50392c
7a4b871
d993379
8d9221b
f595339
1cb57f0
4ee6c48
04127eb
bcaf621
dd802c4
1a46eda
3a9916c
cc23800
486b4af
0a91465
17dab5c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| const fs = require('fs'); | ||
| const fs = require('fs').promises; | ||
| const matter = require('gray-matter'); | ||
| const path = require('path'); | ||
|
|
||
|
|
@@ -98,14 +98,10 @@ function validateDocs(frontmatter) { | |
| * @param {Function} validateFunction - The function used to validate the frontmatter. | ||
| * @param {string} [relativePath=''] - The relative path of the folder for logging purposes. | ||
| */ | ||
| function checkMarkdownFiles(folderPath, validateFunction, relativePath = '') { | ||
| fs.readdir(folderPath, (err, files) => { | ||
| if (err) { | ||
| console.error('Error reading directory:', err); | ||
| return; | ||
| } | ||
|
|
||
| files.forEach(file => { | ||
| async function checkMarkdownFiles(folderPath, validateFunction, relativePath = '') { | ||
| try { | ||
| const files = await fs.readdir(folderPath); | ||
| const filePromises = files.map(async (file) => { | ||
| const filePath = path.join(folderPath, file); | ||
| const relativeFilePath = path.join(relativePath, file); | ||
|
|
||
|
|
@@ -114,17 +110,13 @@ function checkMarkdownFiles(folderPath, validateFunction, relativePath = '') { | |
| return; | ||
| } | ||
|
|
||
| fs.stat(filePath, (err, stats) => { | ||
| if (err) { | ||
| console.error('Error reading file stats:', err); | ||
| return; | ||
| } | ||
| const stats = await fs.stat(filePath); | ||
|
|
||
| // Recurse if directory, otherwise validate markdown file | ||
| if (stats.isDirectory()) { | ||
| checkMarkdownFiles(filePath, validateFunction, relativeFilePath); | ||
| await checkMarkdownFiles(filePath, validateFunction, relativeFilePath); | ||
| } else if (path.extname(file) === '.md') { | ||
| const fileContent = fs.readFileSync(filePath, 'utf-8'); | ||
| const fileContent = await fs.readFile(filePath, 'utf-8'); | ||
| const { data: frontmatter } = matter(fileContent); | ||
|
|
||
| const errors = validateFunction(frontmatter); | ||
|
|
@@ -134,13 +126,33 @@ function checkMarkdownFiles(folderPath, validateFunction, relativePath = '') { | |
| process.exitCode = 1; | ||
| } | ||
| } | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| await Promise.all(filePromises); | ||
| } catch (err) { | ||
| console.error(`Error in directory ${folderPath}:`, err); | ||
| throw err; | ||
| } | ||
| } | ||
|
|
||
| const docsFolderPath = path.resolve(__dirname, '../../markdown/docs'); | ||
| const blogsFolderPath = path.resolve(__dirname, '../../markdown/blog'); | ||
|
|
||
| checkMarkdownFiles(docsFolderPath, validateDocs); | ||
| checkMarkdownFiles(blogsFolderPath, validateBlogs); | ||
| async function main() { | ||
| try { | ||
| await Promise.all([ | ||
| checkMarkdownFiles(docsFolderPath, validateDocs), | ||
| checkMarkdownFiles(blogsFolderPath, validateBlogs) | ||
| ]); | ||
| } catch (error) { | ||
| console.error('Failed to validate markdown files:', error); | ||
| process.exit(1); | ||
| } | ||
| } | ||
|
|
||
| /* istanbul ignore next */ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why this comment is added?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is added to exclude the if block beneath it from coverage reporting. This block ensures that the script runs its main() function only when executed directly (and not when imported as a module). Without this check, the main() function would execute automatically when the script is imported during testing. |
||
| if (require.main === module) { | ||
| main(); | ||
| } | ||
|
|
||
| module.exports = { validateBlogs, validateDocs, checkMarkdownFiles, main, isValidURL }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| const fs = require('fs').promises; | ||
| const path = require('path'); | ||
| const os = require('os'); | ||
| const { | ||
| isValidURL, | ||
| main, | ||
| validateBlogs, | ||
| validateDocs, | ||
| checkMarkdownFiles | ||
| } = require('../../scripts/markdown/check-markdown'); | ||
|
|
||
| describe('Frontmatter Validator', () => { | ||
| let tempDir; | ||
| let mockConsoleError; | ||
| let mockProcessExit; | ||
|
|
||
| beforeEach(async () => { | ||
| mockConsoleError = jest.spyOn(console, 'error').mockImplementation(); | ||
| mockProcessExit = jest.spyOn(process, 'exit').mockImplementation(); | ||
| tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'test-config')); | ||
| }); | ||
|
|
||
| afterEach(async () => { | ||
| mockConsoleError.mockRestore(); | ||
| mockProcessExit.mockRestore(); | ||
| await fs.rm(tempDir, { recursive: true, force: true }); | ||
| }); | ||
|
|
||
| it('validates authors array and returns specific errors', async () => { | ||
| const frontmatter = { | ||
| title: 'Test Blog', | ||
| date: '2024-01-01', | ||
| type: 'blog', | ||
| tags: ['test'], | ||
| cover: 'cover.jpg', | ||
| authors: [{ name: 'John' }, { photo: 'jane.jpg' }, { name: 'Bob', photo: 'bob.jpg', link: 'not-a-url' }] | ||
| }; | ||
|
|
||
| const errors = validateBlogs(frontmatter); | ||
| expect(errors).toEqual(expect.arrayContaining([ | ||
| 'Author at index 0 is missing a photo', | ||
| 'Author at index 1 is missing a name', | ||
| 'Invalid URL for author at index 2: not-a-url' | ||
| ])); | ||
| }); | ||
|
|
||
| it('validates docs frontmatter for required fields', async () => { | ||
| const frontmatter = { title: 123, weight: 'not-a-number' }; | ||
| const errors = validateDocs(frontmatter); | ||
| expect(errors).toEqual(expect.arrayContaining([ | ||
| 'Title is missing or not a string', | ||
| 'Weight is missing or not a number' | ||
| ])); | ||
| }); | ||
|
|
||
| it('checks for errors in markdown files in a directory', async () => { | ||
| await fs.writeFile(path.join(tempDir, 'invalid.md'), `---\ntitle: Invalid Blog\n---`); | ||
| const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(); | ||
|
|
||
| await checkMarkdownFiles(tempDir, validateBlogs); | ||
|
|
||
| expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('Errors in file invalid.md:')); | ||
| mockConsoleLog.mockRestore(); | ||
| }); | ||
|
|
||
| it('returns multiple validation errors for invalid blog frontmatter', async () => { | ||
| const frontmatter = { | ||
| title: 123, | ||
| date: 'invalid-date', | ||
| type: 'blog', | ||
| tags: 'not-an-array', | ||
| cover: ['not-a-string'], | ||
| authors: { name: 'John Doe' } | ||
| }; | ||
| const errors = validateBlogs(frontmatter); | ||
|
|
||
| expect(errors).toEqual([ | ||
| 'Invalid date format: invalid-date', | ||
| 'Tags should be an array', | ||
| 'Cover must be a string', | ||
| 'Authors should be an array']); | ||
| }); | ||
|
|
||
| it('logs error to console when an error occurs in checkMarkdownFiles', async () => { | ||
| const invalidFolderPath = path.join(tempDir, 'non-existent-folder'); | ||
|
|
||
| await expect(checkMarkdownFiles(invalidFolderPath, validateBlogs)) | ||
| .rejects.toThrow('ENOENT'); | ||
|
|
||
| expect(mockConsoleError.mock.calls[0][0]).toContain('Error in directory'); | ||
| }); | ||
|
|
||
| it('skips the "reference/specification" folder during validation', async () => { | ||
| const referenceSpecDir = path.join(tempDir, 'reference', 'specification'); | ||
| await fs.mkdir(referenceSpecDir, { recursive: true }); | ||
| await fs.writeFile(path.join(referenceSpecDir, 'skipped.md'), `---\ntitle: Skipped File\n---`); | ||
|
|
||
| const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(); | ||
|
|
||
| await checkMarkdownFiles(tempDir, validateDocs); | ||
|
|
||
| expect(mockConsoleLog).not.toHaveBeenCalledWith(expect.stringContaining('Errors in file reference/specification/skipped.md')); | ||
| mockConsoleLog.mockRestore(); | ||
| }); | ||
|
|
||
| it('logs and rejects when an exception occurs while processing a file', async () => { | ||
| const filePath = path.join(tempDir, 'invalid.md'); | ||
| await fs.writeFile(filePath, `---\ntitle: Valid Title\n---`); | ||
|
|
||
| const mockReadFile = jest.spyOn(fs, 'readFile').mockRejectedValue(new Error('Test readFile error')); | ||
|
|
||
| await expect(checkMarkdownFiles(tempDir, validateBlogs)).rejects.toThrow('Test readFile error'); | ||
| expect(mockConsoleError).toHaveBeenCalledWith( | ||
| expect.stringContaining(`Error in directory`), | ||
| expect.any(Error) | ||
| ); | ||
|
|
||
| mockReadFile.mockRestore(); | ||
| }); | ||
|
|
||
| it('should handle main function errors and exit with status 1', async () => { | ||
| jest.spyOn(fs, 'readdir').mockRejectedValue(new Error('Test error')); | ||
|
|
||
| await main(); | ||
|
|
||
| expect(mockProcessExit).toHaveBeenCalledWith(1); | ||
|
|
||
| expect(mockConsoleError).toHaveBeenCalledWith( | ||
| 'Failed to validate markdown files:', | ||
| expect.any(Error) | ||
| ); | ||
| }); | ||
|
|
||
| it('should handle successful main function execution', async () => { | ||
|
|
||
| await main(); | ||
|
|
||
| expect(mockConsoleError).not.toHaveBeenCalledWith(); | ||
| }); | ||
|
|
||
| it('should return true or false for URLs', () => { | ||
| expect(isValidURL('http://example.com')).toBe(true); | ||
| expect(isValidURL('https://www.example.com')).toBe(true); | ||
| expect(isValidURL('ftp://ftp.example.com')).toBe(true); | ||
| expect(isValidURL('invalid-url')).toBe(false); | ||
| expect(isValidURL('/path/to/file')).toBe(false); | ||
| expect(isValidURL('www.example.com')).toBe(false); | ||
| }); | ||
|
|
||
| }); |
Uh oh!
There was an error while loading. Please reload this page.