@mdfriday/foundry
Version:
The core engine of MDFriday. Convert Markdown and shortcodes into fully themed static sites – Hugo-style, powered by TypeScript.
293 lines • 13.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const overlaydir_1 = require("../entity/overlaydir");
const type_1 = require("../type");
// Mock dependencies
jest.mock('@internal/domain/fs/vo/overlayoptions');
jest.mock('@pkg/log');
describe('OverlayDir Entity', () => {
let mockFs1;
let mockFs2;
let mockDirOpener1;
let mockDirOpener2;
let mockInfoFunc;
let mockDirsMerger;
let overlayDir;
beforeEach(() => {
mockFs1 = {
name: jest.fn().mockReturnValue('MockFs1'),
open: jest.fn(),
stat: jest.fn(),
};
mockFs2 = {
name: jest.fn().mockReturnValue('MockFs2'),
open: jest.fn(),
stat: jest.fn(),
};
mockDirOpener1 = jest.fn();
mockDirOpener2 = jest.fn();
mockInfoFunc = jest.fn();
mockDirsMerger = jest.fn().mockImplementation((a, b) => [...a, ...b]);
const options = {
name: '/test/dir',
fss: [mockFs1, mockFs2],
dirOpeners: [mockDirOpener1, mockDirOpener2],
info: mockInfoFunc,
merge: mockDirsMerger,
};
overlayDir = new overlaydir_1.OverlayDir(options);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('constructor', () => {
it('should initialize with default options', () => {
const defaultDir = new overlaydir_1.OverlayDir();
expect(defaultDir.name()).toBe('');
expect(defaultDir.fss).toEqual([]);
expect(defaultDir.dirOpeners).toEqual([]);
expect(defaultDir.offset).toBe(0);
expect(defaultDir.closed).toBe(false);
});
it('should initialize with provided options', () => {
expect(overlayDir.name()).toBe('/test/dir');
expect(overlayDir.fss).toEqual([mockFs1, mockFs2]);
expect(overlayDir.dirOpeners).toEqual([mockDirOpener1, mockDirOpener2]);
expect(overlayDir.info).toBe(mockInfoFunc);
expect(overlayDir.merge).toBe(mockDirsMerger);
});
it('should create copies of arrays to avoid mutation', () => {
const originalFss = [mockFs1, mockFs2];
const originalOpeners = [mockDirOpener1, mockDirOpener2];
const options = {
fss: originalFss,
dirOpeners: originalOpeners,
};
const dir = new overlaydir_1.OverlayDir(options);
expect(dir.fss).not.toBe(originalFss);
expect(dir.dirOpeners).not.toBe(originalOpeners);
expect(dir.fss).toEqual(originalFss);
expect(dir.dirOpeners).toEqual(originalOpeners);
});
});
describe('name', () => {
it('should return directory name', () => {
expect(overlayDir.name()).toBe('/test/dir');
});
});
describe('isClosed', () => {
it('should return false when directory is open', () => {
const result = overlayDir.isClosed();
expect(result).toBe(false);
});
it('should return true when closed flag is set', () => {
overlayDir.closed = true;
const result = overlayDir.isClosed();
expect(result).toBe(true);
});
it('should return true when no filesystems or openers', () => {
const emptyDir = new overlaydir_1.OverlayDir({ fss: [], dirOpeners: [] });
const result = emptyDir.isClosed();
expect(result).toBe(true);
});
});
describe('readdir', () => {
beforeEach(() => {
// Mock file objects that will be returned by filesystem open
const mockFile1 = {
readdir: jest.fn().mockResolvedValue([
{ name: () => 'file1.txt', isDir: () => false },
{ name: () => 'dir1', isDir: () => true },
]),
close: jest.fn(),
};
const mockFile2 = {
readdir: jest.fn().mockResolvedValue([
{ name: () => 'file2.txt', isDir: () => false },
{ name: () => 'dir2', isDir: () => true },
]),
close: jest.fn(),
};
mockFs1.open.mockResolvedValue(mockFile1);
mockFs2.open.mockResolvedValue(mockFile2);
// Ensure mockDirsMerger handles arrays correctly (like defaultDirMerger)
mockDirsMerger.mockImplementation((lofi, bofi) => {
const result = [...(lofi || [])];
for (const boFile of (bofi || [])) {
let found = false;
for (const loFile of (lofi || [])) {
if (boFile.name() === loFile.name()) {
found = true;
break;
}
}
if (!found) {
result.push(boFile);
}
}
return result;
});
});
it('should throw error when directory is closed', async () => {
overlayDir.closed = true;
await expect(overlayDir.readdir(10)).rejects.toThrow(type_1.ErrFileClosed);
});
it('should throw existing error if present', async () => {
const testError = new Error('Test error');
overlayDir.err = testError;
await expect(overlayDir.readdir(10)).rejects.toThrow(testError);
});
it('should load and return directory entries', async () => {
const result = await overlayDir.readdir(10);
expect(mockFs1.open).toHaveBeenCalledWith('/test/dir');
expect(mockFs2.open).toHaveBeenCalledWith('/test/dir');
expect(result).toHaveLength(4); // 2 from each filesystem
expect(mockDirsMerger).toHaveBeenCalled();
});
it('should handle root directory path conversion', async () => {
const mockFile1 = {
readdir: jest.fn().mockResolvedValue([
{ name: () => 'root-file.txt', isDir: () => false },
]),
close: jest.fn(),
};
mockFs1.open.mockResolvedValue(mockFile1);
const rootDir = new overlaydir_1.OverlayDir({
name: '/',
fss: [mockFs1],
merge: mockDirsMerger // Use the same merger to ensure consistency
});
const result = await rootDir.readdir(10);
expect(mockFs1.open).toHaveBeenCalledWith(''); // "/" converted to ""
expect(result).toHaveLength(1);
});
it('should return all entries when count is 0 or negative', async () => {
const result = await overlayDir.readdir(0);
expect(result).toHaveLength(4);
expect(overlayDir.err).toEqual(expect.any(Error));
expect(overlayDir.err.message).toBe('EOF');
});
it('should return limited entries when count is positive', async () => {
const result = await overlayDir.readdir(2);
expect(result).toHaveLength(2);
expect(overlayDir.offset).toBe(2);
});
it('should handle subsequent reads with offset', async () => {
await overlayDir.readdir(2); // First read
const result = await overlayDir.readdir(2); // Second read
expect(result).toHaveLength(2);
expect(overlayDir.offset).toBe(4);
});
it('should throw EOF error when no more entries', async () => {
await overlayDir.readdir(4); // Read all entries
await expect(overlayDir.readdir(1)).rejects.toThrow('EOF');
});
it('should handle filesystem errors gracefully', async () => {
mockFs1.open.mockRejectedValue(new Error('Access denied'));
// mockFs2 should still work
const result = await overlayDir.readdir(10);
expect(result).toHaveLength(2); // Only from mockFs2
});
it('should read from directory openers when filesystems fail', async () => {
const mockFile = {
readdir: jest.fn().mockResolvedValue([
{ name: () => 'virtual.txt', isDir: () => false },
]),
close: jest.fn(),
};
mockDirOpener1.mockResolvedValue(mockFile);
mockFs1.open.mockRejectedValue(new Error('Not found'));
mockFs2.open.mockRejectedValue(new Error('Not found'));
const result = await overlayDir.readdir(10);
expect(mockDirOpener1).toHaveBeenCalled();
expect(result).toHaveLength(1);
});
});
describe('readdirnames', () => {
beforeEach(() => {
const mockFile = {
readdir: jest.fn().mockResolvedValue([
{ name: () => 'file1.txt', isDir: () => false },
{ name: () => 'file2.txt', isDir: () => false },
]),
close: jest.fn(),
};
mockFs1.open.mockResolvedValue(mockFile);
mockFs2.open.mockResolvedValue(mockFile);
});
it('should throw error when directory is closed', async () => {
overlayDir.closed = true;
await expect(overlayDir.readdirnames(10)).rejects.toThrow(type_1.ErrFileClosed);
});
it('should return directory entry names', async () => {
const result = await overlayDir.readdirnames(10);
expect(result).toEqual(['file1.txt', 'file2.txt', 'file1.txt', 'file2.txt']);
});
});
describe('File interface methods', () => {
it('should implement close method', async () => {
await expect(overlayDir.close()).resolves.toBeUndefined();
expect(overlayDir.closed).toBe(true);
});
it('should implement read method', async () => {
const buffer = new Uint8Array(10);
await expect(overlayDir.read(buffer)).rejects.toThrow('operation read not supported on directory');
});
it('should implement readAt method', async () => {
const buffer = new Uint8Array(10);
await expect(overlayDir.readAt(buffer, 0)).rejects.toThrow('operation readAt not supported on directory');
});
it('should implement seek method', async () => {
await expect(overlayDir.seek(0, 0)).rejects.toThrow('operation seek not supported on directory');
});
it('should implement write method', async () => {
const buffer = new Uint8Array([1, 2, 3]);
await expect(overlayDir.write(buffer)).rejects.toThrow('operation write not supported on directory');
});
it('should implement writeAt method', async () => {
const buffer = new Uint8Array([1, 2, 3]);
await expect(overlayDir.writeAt(buffer, 0)).rejects.toThrow('operation writeAt not supported on directory');
});
it('should implement stat method', async () => {
const mockFileInfo = { name: () => '/test/dir', isDir: () => true };
mockInfoFunc.mockResolvedValue(mockFileInfo);
const result = await overlayDir.stat();
expect(mockInfoFunc).toHaveBeenCalled();
expect(result).toBe(mockFileInfo);
});
it('should implement sync method', async () => {
await expect(overlayDir.sync()).rejects.toThrow('operation sync not supported on directory');
});
it('should implement truncate method', async () => {
await expect(overlayDir.truncate(100)).rejects.toThrow('operation truncate not supported on directory');
});
it('should implement writeString method', async () => {
await expect(overlayDir.writeString('test')).rejects.toThrow('operation writeString not supported on directory');
});
});
describe('factory functions', () => {
it('should create OverlayDir with newOverlayDir', () => {
const options = { name: 'test' };
const result = (0, overlaydir_1.newOverlayDir)(options);
expect(result).toBeInstanceOf(overlaydir_1.OverlayDir);
expect(result.name()).toBe('test');
});
it('should create OverlayDir with openDir', async () => {
const result = await (0, overlaydir_1.openDir)('/test', mockDirsMerger, mockInfoFunc, [mockFs1]);
expect(result).toBeInstanceOf(overlaydir_1.OverlayDir);
expect(result.name()).toBe('/test');
});
it('should manage directory cache with getDir and releaseDir', async () => {
const dir1 = (0, overlaydir_1.getDir)();
const dir2 = (0, overlaydir_1.getDir)();
// Should return different instances from pool
expect(dir1).toBeInstanceOf(overlaydir_1.OverlayDir);
expect(dir2).toBeInstanceOf(overlaydir_1.OverlayDir);
(0, overlaydir_1.releaseDir)(dir1);
// After release, can get instance from pool
const dir3 = (0, overlaydir_1.getDir)();
expect(dir3).toBeInstanceOf(overlaydir_1.OverlayDir);
});
});
});
//# sourceMappingURL=entity-overlaydir.test.js.map