325 lines
8.7 KiB
JavaScript
325 lines
8.7 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Unit tests for front-matter enrichment
|
|
*/
|
|
|
|
import { promises as fs } from 'fs';
|
|
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
import { enrichFrontmatterOnOpen, extractFrontmatter } from './ensureFrontmatter.mjs';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
const TEST_DIR = path.join(__dirname, '..', 'test-vault-frontmatter');
|
|
|
|
// Test utilities
|
|
async function setupTestDir() {
|
|
await fs.mkdir(TEST_DIR, { recursive: true });
|
|
}
|
|
|
|
async function cleanupTestDir() {
|
|
try {
|
|
await fs.rm(TEST_DIR, { recursive: true, force: true });
|
|
} catch (err) {
|
|
console.warn('Cleanup warning:', err.message);
|
|
}
|
|
}
|
|
|
|
async function createTestFile(filename, content) {
|
|
const filePath = path.join(TEST_DIR, filename);
|
|
await fs.writeFile(filePath, content, 'utf-8');
|
|
return filePath;
|
|
}
|
|
|
|
// Test runner
|
|
const tests = [];
|
|
function test(name, fn) {
|
|
tests.push({ name, fn });
|
|
}
|
|
|
|
// Tests
|
|
test('Should add front-matter to file without any', async () => {
|
|
const filePath = await createTestFile('no-frontmatter.md', '# Hello World\n\nThis is content.');
|
|
|
|
const result = await enrichFrontmatterOnOpen(filePath);
|
|
|
|
if (!result.modified) {
|
|
throw new Error('Expected file to be modified');
|
|
}
|
|
|
|
const content = await fs.readFile(filePath, 'utf-8');
|
|
if (!content.startsWith('---\n')) {
|
|
throw new Error('Expected front-matter to be added');
|
|
}
|
|
|
|
if (!content.includes('titre: no-frontmatter')) {
|
|
throw new Error('Expected titre to be set to filename');
|
|
}
|
|
|
|
if (!content.includes('auteur: Bruno Charest')) {
|
|
throw new Error('Expected auteur to be set');
|
|
}
|
|
|
|
if (!content.includes('favoris: false')) {
|
|
throw new Error('Expected favoris to be false');
|
|
}
|
|
|
|
if (!content.includes('template: false')) {
|
|
throw new Error('Expected template to be false');
|
|
}
|
|
|
|
if (!content.includes('task: false')) {
|
|
throw new Error('Expected task to be false');
|
|
}
|
|
|
|
console.log('✓ Front-matter added successfully');
|
|
});
|
|
|
|
test('Should be idempotent (no changes on second run)', async () => {
|
|
const filePath = await createTestFile('idempotent.md', '# Test\n\nContent here.');
|
|
|
|
// First enrichment
|
|
const result1 = await enrichFrontmatterOnOpen(filePath);
|
|
if (!result1.modified) {
|
|
throw new Error('Expected first enrichment to modify file');
|
|
}
|
|
|
|
const content1 = await fs.readFile(filePath, 'utf-8');
|
|
|
|
// Wait a bit to ensure timestamp would differ if modified
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
|
|
// Second enrichment
|
|
const result2 = await enrichFrontmatterOnOpen(filePath);
|
|
if (result2.modified) {
|
|
throw new Error('Expected second enrichment to NOT modify file (idempotent)');
|
|
}
|
|
|
|
const content2 = await fs.readFile(filePath, 'utf-8');
|
|
|
|
if (content1 !== content2) {
|
|
throw new Error('Expected content to be identical after second enrichment');
|
|
}
|
|
|
|
console.log('✓ Idempotence verified');
|
|
});
|
|
|
|
test('Should preserve existing properties', async () => {
|
|
const initialContent = `---
|
|
titre: Custom Title
|
|
auteur: Bruno Charest
|
|
custom_field: custom value
|
|
favoris: true
|
|
---
|
|
|
|
# Content`;
|
|
|
|
const filePath = await createTestFile('preserve.md', initialContent);
|
|
|
|
const result = await enrichFrontmatterOnOpen(filePath);
|
|
|
|
const content = await fs.readFile(filePath, 'utf-8');
|
|
|
|
if (!content.includes('custom_field: custom value')) {
|
|
throw new Error('Expected custom field to be preserved');
|
|
}
|
|
|
|
if (!content.includes('favoris: true')) {
|
|
throw new Error('Expected favoris: true to be preserved');
|
|
}
|
|
|
|
if (!content.includes('titre: Custom Title')) {
|
|
throw new Error('Expected custom title to be preserved');
|
|
}
|
|
|
|
console.log('✓ Existing properties preserved');
|
|
});
|
|
|
|
test('Should maintain correct key order', async () => {
|
|
const filePath = await createTestFile('order.md', '# Test\n\nContent.');
|
|
|
|
await enrichFrontmatterOnOpen(filePath);
|
|
|
|
const content = await fs.readFile(filePath, 'utf-8');
|
|
|
|
// Extract front-matter
|
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
if (!fmMatch) {
|
|
throw new Error('No front-matter found');
|
|
}
|
|
|
|
const lines = fmMatch[1].split('\n').filter(l => l.trim());
|
|
|
|
// Check order of required keys
|
|
const expectedOrder = [
|
|
'titre:',
|
|
'auteur:',
|
|
'creation_date:',
|
|
'modification_date:',
|
|
'catégorie:',
|
|
'tags:',
|
|
'aliases:',
|
|
'status:',
|
|
'publish:',
|
|
'favoris:',
|
|
'template:',
|
|
'task:',
|
|
'archive:',
|
|
'draft:',
|
|
'private:'
|
|
];
|
|
|
|
let lastIndex = -1;
|
|
for (const key of expectedOrder) {
|
|
const currentIndex = lines.findIndex(l => l.startsWith(key));
|
|
if (currentIndex === -1) {
|
|
throw new Error(`Expected key ${key} not found`);
|
|
}
|
|
if (currentIndex < lastIndex) {
|
|
throw new Error(`Key order incorrect: ${key} appears before previous key`);
|
|
}
|
|
lastIndex = currentIndex;
|
|
}
|
|
|
|
console.log('✓ Key order is correct');
|
|
});
|
|
|
|
test('Should have no blank lines in front-matter', async () => {
|
|
const filePath = await createTestFile('no-blanks.md', '# Test\n\nContent.');
|
|
|
|
await enrichFrontmatterOnOpen(filePath);
|
|
|
|
const content = await fs.readFile(filePath, 'utf-8');
|
|
|
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
if (!fmMatch) {
|
|
throw new Error('No front-matter found');
|
|
}
|
|
|
|
const fmContent = fmMatch[1];
|
|
if (fmContent.includes('\n\n')) {
|
|
throw new Error('Found blank lines in front-matter');
|
|
}
|
|
|
|
console.log('✓ No blank lines in front-matter');
|
|
});
|
|
|
|
test('Should use correct boolean types', async () => {
|
|
const filePath = await createTestFile('booleans.md', '# Test\n\nContent.');
|
|
|
|
await enrichFrontmatterOnOpen(filePath);
|
|
|
|
const fm = await extractFrontmatter(filePath);
|
|
|
|
if (fm.favoris !== false) {
|
|
throw new Error(`Expected favoris to be boolean false, got ${typeof fm.favoris}: ${fm.favoris}`);
|
|
}
|
|
|
|
if (fm.template !== false) {
|
|
throw new Error(`Expected template to be boolean false, got ${typeof fm.template}: ${fm.template}`);
|
|
}
|
|
|
|
if (fm.task !== false) {
|
|
throw new Error(`Expected task to be boolean false, got ${typeof fm.task}: ${fm.task}`);
|
|
}
|
|
|
|
if (fm.publish !== false) {
|
|
throw new Error(`Expected publish to be boolean false, got ${typeof fm.publish}: ${fm.publish}`);
|
|
}
|
|
|
|
console.log('✓ Boolean types are correct');
|
|
});
|
|
|
|
test('Should use correct array types for tags and aliases', async () => {
|
|
const filePath = await createTestFile('arrays.md', '# Test\n\nContent.');
|
|
|
|
await enrichFrontmatterOnOpen(filePath);
|
|
|
|
const fm = await extractFrontmatter(filePath);
|
|
|
|
if (!Array.isArray(fm.tags)) {
|
|
throw new Error(`Expected tags to be array, got ${typeof fm.tags}`);
|
|
}
|
|
|
|
if (!Array.isArray(fm.aliases)) {
|
|
throw new Error(`Expected aliases to be array, got ${typeof fm.aliases}`);
|
|
}
|
|
|
|
if (fm.tags.length !== 0) {
|
|
throw new Error(`Expected tags to be empty array, got length ${fm.tags.length}`);
|
|
}
|
|
|
|
if (fm.aliases.length !== 0) {
|
|
throw new Error(`Expected aliases to be empty array, got length ${fm.aliases.length}`);
|
|
}
|
|
|
|
console.log('✓ Array types are correct');
|
|
});
|
|
|
|
test('Should format dates in ISO 8601 with timezone', async () => {
|
|
const filePath = await createTestFile('dates.md', '# Test\n\nContent.');
|
|
|
|
await enrichFrontmatterOnOpen(filePath);
|
|
|
|
const fm = await extractFrontmatter(filePath);
|
|
|
|
// Check creation_date format
|
|
if (!fm.creation_date || typeof fm.creation_date !== 'string') {
|
|
throw new Error('Expected creation_date to be a string');
|
|
}
|
|
|
|
if (!fm.creation_date.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}$/)) {
|
|
throw new Error(`creation_date format incorrect: ${fm.creation_date}`);
|
|
}
|
|
|
|
// Check modification_date format
|
|
if (!fm.modification_date || typeof fm.modification_date !== 'string') {
|
|
throw new Error('Expected modification_date to be a string');
|
|
}
|
|
|
|
if (!fm.modification_date.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}$/)) {
|
|
throw new Error(`modification_date format incorrect: ${fm.modification_date}`);
|
|
}
|
|
|
|
console.log('✓ Date formats are correct');
|
|
});
|
|
|
|
// Run all tests
|
|
async function runTests() {
|
|
console.log('\n🧪 Running front-matter enrichment tests...\n');
|
|
|
|
await setupTestDir();
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
|
|
for (const { name, fn } of tests) {
|
|
try {
|
|
await fn();
|
|
passed++;
|
|
} catch (err) {
|
|
console.error(`✗ ${name}`);
|
|
console.error(` ${err.message}`);
|
|
failed++;
|
|
}
|
|
}
|
|
|
|
await cleanupTestDir();
|
|
|
|
console.log(`\n📊 Results: ${passed} passed, ${failed} failed\n`);
|
|
|
|
if (failed > 0) {
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Execute if run directly
|
|
if (process.argv[1] === __filename) {
|
|
runTests().catch(err => {
|
|
console.error('Test runner error:', err);
|
|
process.exit(1);
|
|
});
|
|
}
|