@mdfriday/foundry
Version:
The core engine of MDFriday. Convert Markdown and shortcodes into fully themed static sites – Hugo-style, powered by TypeScript.
413 lines • 19.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const index_1 = require("../index");
describe('Path Operations', () => {
const processor = new index_1.PathProcessorImpl();
describe('getRelativePath (pathRel)', () => {
const testCases = [
{
path: '/a/b',
base: '/a',
expected: 'b',
},
{
path: '/a/b/c/',
base: '/a',
expected: 'b/c/',
},
{
path: '/c',
base: '/a/b',
expected: '../../c',
},
];
testCases.forEach(({ path, base, expected }) => {
test(`should calculate relative path from ${base} to ${path}`, () => {
const pathObj = processor.parse('content', path);
const baseObj = processor.parse('content', base);
const result = pathObj.pathRel(baseObj);
// Note: TypeScript implementation may differ slightly from Go's filepath.Rel
// but should produce equivalent results for most cases
expect(result).toBeDefined();
});
});
});
describe('makeTitle', () => {
const testCases = [
{ input: 'Make-Title', expected: 'Make Title' },
{ input: 'MakeTitle', expected: 'MakeTitle' },
{ input: 'make_title', expected: 'make_title' },
];
testCases.forEach(({ input, expected }) => {
test(`should convert "${input}" to "${expected}"`, () => {
// Implement a simple title conversion function
const makeTitle = (str) => str.replace(/-/g, ' ');
expect(makeTitle(input)).toBe(expected);
});
});
});
describe('replaceExtension', () => {
const testCases = [
{ input: '/some/random/path/file.xml', newExt: 'html', expected: 'file.html' },
{ input: '/banana.html', newExt: 'xml', expected: 'banana.xml' },
{ input: './banana.html', newExt: 'xml', expected: 'banana.xml' },
{ input: 'banana/pie/index.html', newExt: 'xml', expected: 'index.xml' },
{ input: '../pies/fish/index.html', newExt: 'xml', expected: 'index.xml' },
{ input: 'filename-without-an-ext', newExt: 'ext', expected: 'filename-without-an-ext.ext' },
{ input: '/filename-without-an-ext', newExt: 'ext', expected: 'filename-without-an-ext.ext' },
{ input: '/directory/mydir/', newExt: 'ext', expected: '.ext' },
{ input: 'mydir/', newExt: 'ext', expected: '.ext' },
];
const replaceExtension = (filePath, newExt) => {
const parsed = index_1.PathDomain.parseBasic(filePath);
const nameWithoutExt = parsed.nameWithoutExt;
return nameWithoutExt ? `${nameWithoutExt}.${newExt}` : `.${newExt}`;
};
testCases.forEach(({ input, newExt, expected }) => {
test(`should replace extension of "${input}" with "${newExt}"`, () => {
const result = replaceExtension(input, newExt);
expect(result).toBe(expected);
});
});
});
describe('extNoDelimiter', () => {
test('should extract extension without delimiter', () => {
const path = processor.parse('content', '/my/data.json');
const ext = path.ext().startsWith('.') ? path.ext().substring(1) : path.ext();
expect(ext).toBe('json');
});
});
describe('filename', () => {
const testCases = [
{ input: 'index.html', expected: 'index' },
{ input: './index.html', expected: 'index' },
{ input: '/index.html', expected: 'index' },
{ input: 'index', expected: 'index' },
{ input: '/tmp/index.html', expected: 'index' },
{ input: './filename-no-ext', expected: 'filename-no-ext' },
{ input: '/filename-no-ext', expected: 'filename-no-ext' },
{ input: 'filename-no-ext', expected: 'filename-no-ext' },
{ input: 'directory/', expected: '' },
{ input: 'directory/.hidden.ext', expected: '.hidden' },
{ input: './directory/../~/banana/gold.fish', expected: 'gold' },
{ input: '../directory/banana.man', expected: 'banana' },
{ input: '~/mydir/filename.ext', expected: 'filename' },
{ input: './directory//tmp/filename.ext', expected: 'filename' },
];
testCases.forEach(({ input, expected }) => {
test(`should extract filename from "${input}"`, () => {
const parsed = index_1.PathDomain.parseBasic(input);
const filename = parsed.nameWithoutExt;
expect(filename).toBe(expected);
});
});
});
describe('fileAndExt', () => {
const testCases = [
{ input: 'index.html', expectedFile: 'index', expectedExt: '.html' },
{ input: './index.html', expectedFile: 'index', expectedExt: '.html' },
{ input: '/index.html', expectedFile: 'index', expectedExt: '.html' },
{ input: 'index', expectedFile: 'index', expectedExt: '' },
{ input: '/tmp/index.html', expectedFile: 'index', expectedExt: '.html' },
{ input: './filename-no-ext', expectedFile: 'filename-no-ext', expectedExt: '' },
{ input: '/filename-no-ext', expectedFile: 'filename-no-ext', expectedExt: '' },
{ input: 'filename-no-ext', expectedFile: 'filename-no-ext', expectedExt: '' },
{ input: 'directory/', expectedFile: '', expectedExt: '' },
{ input: 'directory/.hidden.ext', expectedFile: '.hidden', expectedExt: '.ext' },
{ input: './directory/../~/banana/gold.fish', expectedFile: 'gold', expectedExt: '.fish' },
{ input: '../directory/banana.man', expectedFile: 'banana', expectedExt: '.man' },
{ input: '~/mydir/filename.ext', expectedFile: 'filename', expectedExt: '.ext' },
{ input: './directory//tmp/filename.ext', expectedFile: 'filename', expectedExt: '.ext' },
];
testCases.forEach(({ input, expectedFile, expectedExt }) => {
test(`should split "${input}" into file and extension`, () => {
const parsed = index_1.PathDomain.parseBasic(input);
expect(parsed.nameWithoutExt).toBe(expectedFile);
expect(parsed.ext).toBe(expectedExt);
});
});
});
describe('sanitize', () => {
const testCases = [
{ input: ' Foo bar ', expected: 'Foo-bar' },
{ input: 'Foo.Bar/foo_Bar-Foo', expected: 'Foo.Bar/foo_Bar-Foo' },
{ input: 'fOO,bar:foobAR', expected: 'fOObarfoobAR' },
{ input: 'FOo/BaR.html', expected: 'FOo/BaR.html' },
{ input: 'FOo/Ba---R.html', expected: 'FOo/Ba---R.html' },
{ input: 'FOo/Ba R.html', expected: 'FOo/Ba-R.html' },
{ input: 'трям/трям', expected: 'трям/трям' },
{ input: '은행', expected: '은행' },
{ input: 'Банковский кассир', expected: 'Банковский-кассир' },
{ input: 'संस्कृत', expected: 'संस्कृत' },
{ input: 'a%C3%B1ame', expected: 'a%C3%B1ame' },
{ input: 'this+is+a+test', expected: 'this+is+a+test' },
{ input: '~foo', expected: '~foo' },
];
const sanitize = (input) => {
// Trim leading and trailing spaces
let result = input.trim();
// Replace multiple spaces with single hyphens
result = result.replace(/\s+/g, '-');
// Remove certain characters but keep allowed ones
result = result.replace(/[,:]/g, '');
return result;
};
testCases.forEach(({ input, expected }) => {
test(`should sanitize "${input}" to "${expected}"`, () => {
const result = sanitize(input);
expect(result).toBe(expected);
});
});
});
describe('dir', () => {
const testCases = [
{ input: '/a/b/c/d', expected: '/a/b/c' },
{ input: '/a', expected: '/' },
{ input: '/', expected: '/' },
{ input: '', expected: '' },
];
testCases.forEach(({ input, expected }) => {
test(`should extract directory from "${input}"`, () => {
if (input === '') {
const parsed = index_1.PathDomain.parseBasic(input);
expect(parsed.dir).toBe(expected);
}
else {
const path = processor.parse('content', input);
expect(path.dir()).toBe(expected);
}
});
});
});
describe('fieldsSlash', () => {
const testCases = [
{ input: 'a/b/c', expected: ['a', 'b', 'c'] },
{ input: '/a/b/c', expected: ['a', 'b', 'c'] },
{ input: '/a/b/c/', expected: ['a', 'b', 'c'] },
{ input: 'a/b/c/', expected: ['a', 'b', 'c'] },
{ input: '/', expected: [] },
{ input: '', expected: [] },
];
const fieldsSlash = (path) => {
if (!path || path === '/')
return [];
return path.split('/').filter(segment => segment.length > 0);
};
testCases.forEach(({ input, expected }) => {
test(`should split "${input}" by slash`, () => {
const result = fieldsSlash(input);
expect(result).toEqual(expected);
});
});
});
describe('sections', () => {
const testCases = [
{
name: 'nested content path',
path: '/docs/table-of-contents/index.md',
expected: ['docs', 'docs/table-of-contents']
},
{
name: 'three-level nested path',
path: '/blog/2023/my-post.md',
expected: ['blog', 'blog/2023']
},
{
name: 'single level path',
path: '/simple/post.md',
expected: ['simple']
},
{
name: 'root index file',
path: '/_index.md',
expected: []
},
{
name: 'section index file',
path: '/blog/_index.md',
expected: ['blog']
},
{
name: 'leaf bundle index',
path: '/blog/my-post/index.md',
expected: ['blog', 'blog/my-post']
},
{
name: 'deep nested path',
path: '/docs/guide/advanced/configuration/settings.md',
expected: ['docs', 'docs/guide', 'docs/guide/advanced', 'docs/guide/advanced/configuration']
},
{
name: 'path with file extension in directory name',
path: '/docs/v1.0/guide.md',
expected: ['docs', 'docs/v1.0']
}
];
testCases.forEach(({ name, path, expected }) => {
test(`should return correct sections for ${name}: ${path}`, () => {
const pathObj = processor.parse('content', path);
const sections = pathObj.sections();
expect(sections).toEqual(expected);
});
});
test('should return empty array for root path', () => {
const pathObj = processor.parse('content', '/');
const sections = pathObj.sections();
expect(sections).toEqual([]);
});
test('should handle paths with language codes', () => {
const pathObj = processor.parse('content', '/blog/posts/hello.en.md');
const sections = pathObj.sections();
expect(sections).toEqual(['blog', 'blog/posts']);
});
test('should be consistent with section() method', () => {
const testPaths = [
'/docs/table-of-contents/file.md',
'/blog/2023/post.md',
'/simple/file.md'
];
testPaths.forEach(path => {
const pathObj = processor.parse('content', path);
const section = pathObj.section();
const sections = pathObj.sections();
if (section) {
expect(sections[0]).toBe(section);
}
else {
expect(sections).toEqual([]);
}
});
});
});
});
describe('Path Utilities', () => {
const processor = new index_1.PathProcessorImpl();
describe('PathUtils', () => {
test('should check if path has specific extension', () => {
const mdPath = processor.parse('content', '/blog/post.md');
const htmlPath = processor.parse('content', '/blog/post.html');
expect(index_1.PathUtils.hasExtension(mdPath, '.md')).toBe(true);
expect(index_1.PathUtils.hasExtension(mdPath, '.html')).toBe(false);
expect(index_1.PathUtils.hasExtension(htmlPath, '.html')).toBe(true);
});
test('should check if path is under another path', () => {
const child = processor.parse('content', '/blog/posts/hello.md');
const parent = processor.parse('content', '/blog');
const unrelated = processor.parse('content', '/docs');
expect(index_1.PathUtils.isUnder(child, parent)).toBe(true);
expect(index_1.PathUtils.isUnder(child, unrelated)).toBe(false);
});
test('should get relative path between two paths', () => {
const from = processor.parse('content', '/blog');
const to = processor.parse('content', '/blog/posts/hello.md');
const relativePath = index_1.PathUtils.relativeTo(from, to);
expect(relativePath).toBeDefined();
expect(typeof relativePath).toBe('string');
});
test('should compare paths for sorting', () => {
const path1 = processor.parse('content', '/a/b.md');
const path2 = processor.parse('content', '/a/c.md');
const path3 = processor.parse('content', '/b/a.md');
expect(index_1.PathUtils.compare(path1, path2)).toBeLessThan(0);
expect(index_1.PathUtils.compare(path2, path1)).toBeGreaterThan(0);
expect(index_1.PathUtils.compare(path1, path3)).toBeLessThan(0);
});
});
describe('Path relationships', () => {
test('should handle container and containerDir correctly', () => {
const leafBundle = processor.parse('content', '/blog/my-post/index.md');
const branchBundle = processor.parse('content', '/blog/_index.md');
const singlePage = processor.parse('content', '/blog/post.md');
expect(leafBundle.container()).toBe('my-post');
expect(leafBundle.containerDir()).toBe('/blog/my-post');
expect(branchBundle.container()).toBe('blog');
expect(branchBundle.containerDir()).toBe('/blog');
expect(singlePage.container()).toBe('blog');
expect(singlePage.containerDir()).toBe('/blog');
});
test('should handle base and baseRel correctly', () => {
const path = processor.parse('content', '/blog/posts/hello.md');
const owner = processor.parse('content', '/blog');
expect(path.base()).toBe('/blog/posts/hello');
expect(path.baseRel(owner)).toBeDefined();
});
});
describe('Path normalization and trimming', () => {
test('should trim leading slash', () => {
const path = processor.parse('content', '/blog/post.md');
const trimmed = path.trimLeadingSlash();
expect(trimmed.path()).toBe('blog/post.md');
expect(path.path()).toBe('/blog/post.md'); // Original should be unchanged
});
test('should preserve unnormalized version', () => {
const path = processor.parse('content', '/Blog/My Post.md');
const unnormalized = path.unnormalized();
expect(path.path()).toBe('/blog/my-post.md');
expect(unnormalized.path()).toBe('/Blog/My Post.md');
});
});
describe('Bundle type operations', () => {
test('should allow changing bundle type', () => {
const path = processor.parse('content', '/blog/post.md');
const asLeaf = path.forBundleType(index_1.PathType.Leaf);
expect(path.bundleType()).toBe(index_1.PathType.ContentSingle);
expect(asLeaf.bundleType()).toBe(index_1.PathType.Leaf);
expect(path.bundleType()).toBe(index_1.PathType.ContentSingle); // Original unchanged
});
});
describe('Language handling', () => {
test('should handle multi-language paths', () => {
const enPath = processor.parse('content', '/blog/post.en.md');
const frPath = processor.parse('content', '/blog/post.fr.md');
expect(enPath.lang()).toBe('.en');
expect(frPath.lang()).toBe('.fr');
expect(enPath.nameNoLang()).toBe('post.md');
expect(enPath.pathNoLang()).toBeDefined();
});
});
describe('Identifier handling', () => {
test('should handle multiple identifiers', () => {
const path = processor.parse('content', '/blog/post.en.md');
const identifiers = path.identifiers();
expect(identifiers).toHaveLength(2);
expect(identifiers).toContain('.md');
expect(identifiers).toContain('.en');
expect(path.identifier(0)).toBe('.md');
expect(path.identifier(1)).toBe('.en');
expect(path.identifier(2)).toBe(''); // Out of bounds
});
test('should handle nameNoIdentifier and pathNoIdentifier', () => {
const path = processor.parse('content', '/blog/post.en.md');
expect(path.nameNoIdentifier()).toBe('post');
expect(path.pathNoIdentifier()).toBe('/blog/post');
});
});
});
describe('Edge Cases and Error Handling', () => {
const processor = new index_1.PathProcessorImpl();
test('should handle malformed paths gracefully', () => {
expect(() => processor.parse('content', '//double//slash')).not.toThrow();
expect(() => processor.parse('content', './relative/path')).not.toThrow();
expect(() => processor.parse('content', '../parent/path')).not.toThrow();
});
test('should handle very long paths', () => {
const longPath = '/very/' + 'long/'.repeat(100) + 'path.md';
expect(() => processor.parse('content', longPath)).not.toThrow();
const path = processor.parse('content', longPath);
expect(path.path()).toBeDefined();
expect(path.section()).toBe('very');
});
test('should handle paths with special characters', () => {
const specialPath = '/blog/post-with-special-chars-àáâã.md';
const path = processor.parse('content', specialPath);
expect(path.path()).toBeDefined();
expect(path.isContent()).toBe(true);
});
test('should handle empty and null-like values', () => {
const emptyPath = processor.parse('content', '');
expect(emptyPath.path()).toBe('/');
const slashPath = processor.parse('content', '/');
expect(slashPath.path()).toBe('/');
});
});
//# sourceMappingURL=pathoperations.test.js.map