@mdfriday/foundry
Version:
The core engine of MDFriday. Convert Markdown and shortcodes into fully themed static sites – Hugo-style, powered by TypeScript.
295 lines • 14.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const walkway_1 = require("../vo/walkway");
const type_1 = require("../type");
// Mock dependencies
jest.mock('@pkg/log', () => ({
getDomainLogger: () => ({
error: jest.fn(),
info: jest.fn(),
debug: jest.fn(),
}),
}));
jest.mock('../vo/fileinfo', () => ({
newFileInfoWithMeta: jest.fn().mockImplementation((fileInfo, meta) => ({
...fileInfo,
fileName: meta.fileName,
relativeFilename: meta.relativeFilename,
component: meta.component,
root: meta.root,
open: meta.open,
})),
}));
jest.mock('../vo/filemeta', () => ({
newFileMeta: jest.fn().mockImplementation((filename) => ({
fileName: jest.fn().mockReturnValue(filename),
relativeFilename: jest.fn().mockReturnValue(filename),
component: jest.fn().mockReturnValue(''),
root: jest.fn().mockReturnValue(''),
open: jest.fn(),
setOpenFunc: jest.fn(),
})),
}));
describe('Walkway Value Object', () => {
let mockFs;
let mockWalkCallback;
let mockWalkFn;
let walkway;
beforeEach(() => {
mockWalkFn = jest.fn();
mockWalkCallback = {
walkFn: mockWalkFn,
};
mockFs = {
name: jest.fn().mockReturnValue('MockFs'),
create: jest.fn(),
mkdir: jest.fn(),
mkdirAll: jest.fn(),
open: jest.fn(),
openFile: jest.fn(),
remove: jest.fn(),
removeAll: jest.fn(),
rename: jest.fn(),
stat: jest.fn(),
chmod: jest.fn(),
chown: jest.fn(),
chtimes: jest.fn(),
};
});
afterEach(() => {
jest.clearAllMocks();
});
describe('constructor', () => {
it('should initialize with filesystem and callback', () => {
walkway = new walkway_1.Walkway(mockFs, mockWalkCallback);
expect(walkway).toBeDefined();
expect(walkway.fs).toBe(mockFs);
expect(walkway.cb).toBe(mockWalkCallback);
expect(walkway.root).toBe('');
expect(walkway.walked).toBe(false);
});
it('should throw error when fs is not provided', () => {
expect(() => new walkway_1.Walkway(null, mockWalkCallback)).toThrow('fs must be set');
});
it('should throw error when walkFn is not provided', () => {
const invalidCallback = {};
expect(() => new walkway_1.Walkway(mockFs, invalidCallback)).toThrow('walkFn must be set');
});
it('should throw error when callback is not provided', () => {
expect(() => new walkway_1.Walkway(mockFs, null)).toThrow('Cannot read properties of null');
});
});
describe('walkWith', () => {
beforeEach(() => {
walkway = new walkway_1.Walkway(mockFs, mockWalkCallback);
});
it('should set root and config before walking', async () => {
const config = { sortDirEntries: true };
const mockFileInfo = createMockFileInfo('test.txt', false);
mockFs.stat.mockResolvedValue(mockFileInfo);
await walkway.walkWith('/test/root', config);
expect(walkway.root).toBe('/test/root');
expect(walkway.cfg).toBe(config);
expect(mockFs.stat).toHaveBeenCalledWith('/test/root');
});
it('should handle empty root path', async () => {
const mockFileInfo = createMockFileInfo('', true);
mockFs.stat.mockResolvedValue(mockFileInfo);
const mockFile = createMockFile([]);
mockFs.open.mockResolvedValue(mockFile);
await walkway.walkWith('', {});
expect(mockFs.stat).toHaveBeenCalledWith('');
});
});
describe('walk', () => {
beforeEach(() => {
walkway = new walkway_1.Walkway(mockFs, mockWalkCallback);
});
it('should throw error if already walked', async () => {
const mockFileInfo = createMockFileInfo('test.txt', false);
mockFs.stat.mockResolvedValue(mockFileInfo);
// First walk
await walkway.walkWith('/test', {});
// Second walk should fail
await expect(walkway.walk()).rejects.toThrow('this walkway is already walked');
});
it('should walk single file', async () => {
const mockFileInfo = createMockFileInfo('test.txt', false);
mockFs.stat.mockResolvedValue(mockFileInfo);
await walkway.walkWith('/test.txt', {});
expect(mockFs.stat).toHaveBeenCalledWith('/test.txt');
expect(mockWalkFn).toHaveBeenCalledWith('/test.txt', expect.any(Object));
});
it('should walk directory with files', async () => {
const mockDirInfo = createMockFileInfo('testdir', true);
const mockFileInfo1 = createMockFileInfo('file1.txt', false);
const mockFileInfo2 = createMockFileInfo('file2.txt', false);
mockFs.stat
.mockResolvedValueOnce(mockDirInfo) // Directory stat
.mockResolvedValueOnce(mockFileInfo1) // file1.txt stat
.mockResolvedValueOnce(mockFileInfo2); // file2.txt stat
const mockFile = createMockFile([mockFileInfo1, mockFileInfo2]);
mockFs.open.mockResolvedValue(mockFile);
await walkway.walkWith('/testdir', {});
expect(mockWalkFn).toHaveBeenCalledTimes(3); // Directory + 2 files
expect(mockWalkFn).toHaveBeenCalledWith('/testdir', expect.any(Object));
expect(mockWalkFn).toHaveBeenCalledWith('/testdir/file1.txt', expect.any(Object));
expect(mockWalkFn).toHaveBeenCalledWith('/testdir/file2.txt', expect.any(Object));
});
it('should sort directory entries when configured', async () => {
const mockDirInfo = createMockFileInfo('testdir', true);
const mockFileInfo1 = createMockFileInfo('zebra.txt', false);
const mockFileInfo2 = createMockFileInfo('alpha.txt', false);
mockFs.stat
.mockResolvedValueOnce(mockDirInfo)
.mockResolvedValueOnce(mockFileInfo2) // alpha.txt (sorted first)
.mockResolvedValueOnce(mockFileInfo1); // zebra.txt (sorted second)
const mockFile = createMockFile([mockFileInfo1, mockFileInfo2]); // Unsorted order
mockFs.open.mockResolvedValue(mockFile);
await walkway.walkWith('/testdir', { sortDirEntries: true });
expect(mockWalkFn).toHaveBeenCalledWith('/testdir/alpha.txt', expect.any(Object));
expect(mockWalkFn).toHaveBeenCalledWith('/testdir/zebra.txt', expect.any(Object));
});
it('should handle ErrSkipDir for directories', async () => {
const mockDirInfo = createMockFileInfo('skipdir', true);
mockFs.stat.mockResolvedValue(mockDirInfo);
mockWalkFn.mockImplementation((path, info) => {
if (info.isDir && info.isDir()) {
throw type_1.ErrSkipDir;
}
});
await walkway.walkWith('/skipdir', {});
expect(mockWalkFn).toHaveBeenCalledTimes(1);
expect(mockFs.open).not.toHaveBeenCalled(); // Should not read directory contents
});
it('should handle filesystem stat errors', async () => {
mockFs.stat.mockRejectedValue(new Error('File not found'));
await expect(walkway.walkWith('/nonexistent', {})).rejects.toThrow('walk: stat: File not found');
});
it('should handle filesystem readdir errors', async () => {
const mockDirInfo = createMockFileInfo('testdir', true);
mockFs.stat.mockResolvedValue(mockDirInfo);
mockFs.open.mockRejectedValue(new Error('Cannot read directory'));
await expect(walkway.walkWith('/testdir', {})).rejects.toThrow('walk: readdir: Cannot read directory');
});
it('should handle walkFn errors', async () => {
const mockFileInfo = createMockFileInfo('test.txt', false);
mockFs.stat.mockResolvedValue(mockFileInfo);
mockWalkFn.mockRejectedValue(new Error('Walk function failed'));
await expect(walkway.walkWith('/test.txt', {})).rejects.toThrow('Walk function failed');
});
it('should handle nested directories', async () => {
const mockRootDir = createMockFileInfo('root', true);
const mockSubDir = createMockFileInfo('subdir', true);
const mockFile = createMockFileInfo('file.txt', false);
mockFs.stat
.mockResolvedValueOnce(mockRootDir) // /root
.mockResolvedValueOnce(mockSubDir) // /root/subdir
.mockResolvedValueOnce(mockFile); // /root/subdir/file.txt
const mockRootFile = createMockFile([mockSubDir]);
const mockSubFile = createMockFile([mockFile]);
const mockLeafFile = createMockFile([]);
mockFs.open
.mockResolvedValueOnce(mockRootFile) // Open /root
.mockResolvedValueOnce(mockSubFile) // Open /root/subdir
.mockResolvedValueOnce(mockLeafFile); // Open /root/subdir/file.txt (if needed)
await walkway.walkWith('/root', {});
expect(mockWalkFn).toHaveBeenCalledTimes(3);
expect(mockWalkFn).toHaveBeenCalledWith('/root', expect.any(Object));
expect(mockWalkFn).toHaveBeenCalledWith('/root/subdir', expect.any(Object));
expect(mockWalkFn).toHaveBeenCalledWith('/root/subdir/file.txt', expect.any(Object));
});
});
describe('error handling', () => {
beforeEach(() => {
walkway = new walkway_1.Walkway(mockFs, mockWalkCallback);
});
it('should handle checkErr method for specific errors', async () => {
const mockFileInfo = createMockFileInfo('test.txt', false);
mockFs.stat.mockResolvedValue(mockFileInfo);
// Mock checkErr to return true (ignore error)
jest.spyOn(walkway, 'checkErr').mockReturnValue(true);
// This should not throw despite the stat error
mockFs.stat.mockRejectedValueOnce(new Error('Ignored error'));
// The walk should complete without throwing
await walkway.walkWith('/test.txt', {});
});
it('should handle path joining correctly', () => {
// Since joinPath is not a public method, we'll test path joining through walkWith
const mockFileInfo = createMockFileInfo('test.txt', false);
mockFs.stat.mockResolvedValue(mockFileInfo);
expect(async () => await walkway.walkWith('/root/file.txt', {})).not.toThrow();
});
it('should handle root path joining', () => {
// Test with root path
const mockFileInfo = createMockFileInfo('file.txt', false);
mockFs.stat.mockResolvedValue(mockFileInfo);
expect(async () => await walkway.walkWith('/', {})).not.toThrow();
});
it('should handle empty path components', () => {
// Test with empty path
const mockFileInfo = createMockFileInfo('', false);
mockFs.stat.mockResolvedValue(mockFileInfo);
expect(async () => await walkway.walkWith('', {})).not.toThrow();
});
});
describe('configuration options', () => {
beforeEach(() => {
walkway = new walkway_1.Walkway(mockFs, mockWalkCallback);
});
it('should respect sortDirEntries option', async () => {
const mockDirInfo = createMockFileInfo('testdir', true);
const entries = [
createMockFileInfo('c.txt', false),
createMockFileInfo('a.txt', false),
createMockFileInfo('b.txt', false),
];
mockFs.stat
.mockResolvedValueOnce(mockDirInfo)
.mockResolvedValueOnce(entries[1]) // a.txt
.mockResolvedValueOnce(entries[2]) // b.txt
.mockResolvedValueOnce(entries[0]); // c.txt
const mockFile = createMockFile(entries);
mockFs.open.mockResolvedValue(mockFile);
await walkway.walkWith('/testdir', { sortDirEntries: true });
// Verify the order of calls (after directory itself)
expect(mockWalkFn).toHaveBeenNthCalledWith(2, '/testdir/a.txt', expect.any(Object));
expect(mockWalkFn).toHaveBeenNthCalledWith(3, '/testdir/b.txt', expect.any(Object));
expect(mockWalkFn).toHaveBeenNthCalledWith(4, '/testdir/c.txt', expect.any(Object));
});
});
// Helper functions
function createMockFileInfo(name, isDir) {
return {
name: jest.fn().mockReturnValue(name),
size: jest.fn().mockReturnValue(isDir ? 0 : 100),
mode: jest.fn().mockReturnValue(isDir ? 0o755 : 0o644),
modTime: jest.fn().mockReturnValue(new Date()),
isDir: jest.fn().mockReturnValue(isDir),
sys: jest.fn().mockReturnValue(null),
fileName: jest.fn().mockReturnValue(`/${name}`),
relativeFilename: jest.fn().mockReturnValue(name),
component: jest.fn().mockReturnValue(''),
root: jest.fn().mockReturnValue(''),
open: jest.fn(),
};
}
function createMockFile(entries) {
return {
close: jest.fn(),
read: jest.fn(),
readAt: jest.fn(),
seek: jest.fn(),
write: jest.fn(),
writeAt: jest.fn(),
name: jest.fn(),
readdir: jest.fn().mockResolvedValue(entries),
readdirnames: jest.fn(),
stat: jest.fn(),
sync: jest.fn(),
truncate: jest.fn(),
writeString: jest.fn(),
};
}
});
//# sourceMappingURL=vo-walkway.test.js.map