Skip to content

Commit

Permalink
Merge branch 'master' into laslas
Browse files Browse the repository at this point in the history
  • Loading branch information
anshgoyalevil authored Dec 14, 2024
2 parents 235e948 + 55f0147 commit 2404831
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 20 deletions.
52 changes: 32 additions & 20 deletions scripts/markdown/check-markdown.js
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');

Expand Down Expand Up @@ -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);

Expand All @@ -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);
Expand All @@ -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 */
if (require.main === module) {
main();
}

module.exports = { validateBlogs, validateDocs, checkMarkdownFiles, main, isValidURL };
150 changes: 150 additions & 0 deletions tests/markdown/check-markdown.test.js
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);
});

});

0 comments on commit 2404831

Please sign in to comment.