BREAKING CHANGE(SmartFileFactory): Refactor to in-memory file API and introduce SmartFileFactory; delegate filesystem operations to @push.rocks/smartfs; bump to 12.0.0
This commit is contained in:
95
test/helpers/mock-smartfs.ts
Normal file
95
test/helpers/mock-smartfs.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Mock SmartFs implementation for testing until @push.rocks/smartfs is available
|
||||
* This wraps fs-extra to provide the SmartFs interface
|
||||
*/
|
||||
import { ensureDir, pathExists, remove, copy } from 'fs-extra';
|
||||
import { promises as fsPromises, createReadStream, createWriteStream } from 'fs';
|
||||
import * as path from 'path';
|
||||
import { Readable, Writable } from 'stream';
|
||||
|
||||
export class MockSmartFs {
|
||||
public file(filePath: string) {
|
||||
return {
|
||||
async read(): Promise<string | Buffer> {
|
||||
return await fsPromises.readFile(filePath);
|
||||
},
|
||||
async write(content: string | Buffer): Promise<void> {
|
||||
await ensureDir(path.dirname(filePath));
|
||||
await fsPromises.writeFile(filePath, content);
|
||||
},
|
||||
async exists(): Promise<boolean> {
|
||||
return await pathExists(filePath);
|
||||
},
|
||||
async delete(): Promise<void> {
|
||||
await remove(filePath);
|
||||
},
|
||||
async stat(): Promise<any> {
|
||||
return await fsPromises.stat(filePath);
|
||||
},
|
||||
async readStream(): Promise<Readable> {
|
||||
return Promise.resolve(createReadStream(filePath));
|
||||
},
|
||||
async writeStream(): Promise<Writable> {
|
||||
await ensureDir(path.dirname(filePath));
|
||||
return Promise.resolve(createWriteStream(filePath));
|
||||
},
|
||||
async copy(dest: string): Promise<void> {
|
||||
await copy(filePath, dest);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public directory(dirPath: string) {
|
||||
return {
|
||||
async list(options?: { recursive?: boolean }): Promise<Array<{ path: string; isFile: boolean; isDirectory: boolean }>> {
|
||||
const entries: Array<{ path: string; isFile: boolean; isDirectory: boolean }> = [];
|
||||
|
||||
if (options?.recursive) {
|
||||
// Recursive listing
|
||||
const walk = async (dir: string) => {
|
||||
const items = await fsPromises.readdir(dir);
|
||||
for (const item of items) {
|
||||
const fullPath = path.join(dir, item);
|
||||
const stats = await fsPromises.stat(fullPath);
|
||||
|
||||
if (stats.isFile()) {
|
||||
entries.push({ path: fullPath, isFile: true, isDirectory: false });
|
||||
} else if (stats.isDirectory()) {
|
||||
entries.push({ path: fullPath, isFile: false, isDirectory: true });
|
||||
await walk(fullPath);
|
||||
}
|
||||
}
|
||||
};
|
||||
await walk(dirPath);
|
||||
} else {
|
||||
// Non-recursive listing
|
||||
const items = await fsPromises.readdir(dirPath);
|
||||
for (const item of items) {
|
||||
const fullPath = path.join(dirPath, item);
|
||||
const stats = await fsPromises.stat(fullPath);
|
||||
entries.push({
|
||||
path: fullPath,
|
||||
isFile: stats.isFile(),
|
||||
isDirectory: stats.isDirectory(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
},
|
||||
async create(options?: { recursive?: boolean }): Promise<void> {
|
||||
if (options?.recursive) {
|
||||
await ensureDir(dirPath);
|
||||
} else {
|
||||
await fsPromises.mkdir(dirPath);
|
||||
}
|
||||
},
|
||||
async exists(): Promise<boolean> {
|
||||
return await pathExists(dirPath);
|
||||
},
|
||||
async delete(): Promise<void> {
|
||||
await remove(dirPath);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,23 @@
|
||||
import * as path from 'path';
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import * as smartfile from '../ts/index.js'; // adjust the import path as needed
|
||||
import * as smartfile from '../ts/index.js';
|
||||
import { MockSmartFs } from './helpers/mock-smartfs.js';
|
||||
|
||||
// Create factory with MockSmartFs
|
||||
const mockFs = new MockSmartFs();
|
||||
const factory = new smartfile.SmartFileFactory(mockFs);
|
||||
|
||||
// Test assets path
|
||||
const testAssetsPath = './test/testassets/';
|
||||
|
||||
// ---------------------------
|
||||
// StreamFile tests
|
||||
// StreamFile Factory Tests
|
||||
// ---------------------------
|
||||
|
||||
tap.test(
|
||||
'StreamFile.fromPath should create a StreamFile from a file path',
|
||||
'SmartFileFactory.streamFromPath() -> should create a StreamFile from a file path',
|
||||
async () => {
|
||||
const streamFile = await smartfile.StreamFile.fromPath(
|
||||
const streamFile = await factory.streamFromPath(
|
||||
path.join(testAssetsPath, 'mytest.json'),
|
||||
);
|
||||
expect(streamFile).toBeInstanceOf(smartfile.StreamFile);
|
||||
@@ -22,75 +27,128 @@ tap.test(
|
||||
);
|
||||
|
||||
tap.test(
|
||||
'StreamFile.fromUrl should create a StreamFile from a URL',
|
||||
async () => {
|
||||
const streamFile = await smartfile.StreamFile.fromUrl(
|
||||
'http://example.com/somefile.json',
|
||||
);
|
||||
expect(streamFile).toBeInstanceOf(smartfile.StreamFile);
|
||||
},
|
||||
);
|
||||
|
||||
tap.test(
|
||||
'StreamFile.fromBuffer should create a StreamFile from a Buffer',
|
||||
'SmartFileFactory.streamFromBuffer() -> should create a StreamFile from a Buffer',
|
||||
async () => {
|
||||
const buffer = Buffer.from('Some content');
|
||||
const streamFile = smartfile.StreamFile.fromBuffer(
|
||||
const streamFile = factory.streamFromBuffer(
|
||||
buffer,
|
||||
'bufferfile.txt',
|
||||
);
|
||||
expect(streamFile).toBeInstanceOf(smartfile.StreamFile);
|
||||
const content = await streamFile.getContentAsBuffer();
|
||||
expect(content.toString()).toEqual('Some content');
|
||||
},
|
||||
);
|
||||
|
||||
tap.test('StreamFile should write the stream to disk', async () => {
|
||||
const streamFile = await smartfile.StreamFile.fromPath(
|
||||
tap.test(
|
||||
'SmartFileFactory.streamFromStream() -> should create a StreamFile from a stream',
|
||||
async () => {
|
||||
const { Readable } = await import('stream');
|
||||
const stream = new Readable();
|
||||
stream.push('stream content');
|
||||
stream.push(null);
|
||||
|
||||
const streamFile = factory.streamFromStream(stream, 'streamfile.txt', false);
|
||||
expect(streamFile).toBeInstanceOf(smartfile.StreamFile);
|
||||
},
|
||||
);
|
||||
|
||||
// ---------------------------
|
||||
// StreamFile Instance Tests
|
||||
// ---------------------------
|
||||
|
||||
tap.test('StreamFile -> should write the stream to disk', async () => {
|
||||
const streamFile = await factory.streamFromPath(
|
||||
path.join(testAssetsPath, 'mytest.json'),
|
||||
);
|
||||
await streamFile.writeToDisk(
|
||||
path.join(testAssetsPath, 'temp', 'mytest.json'),
|
||||
);
|
||||
// Verify the file was written
|
||||
expect(
|
||||
// We'll use the fileExists method from your smartfile library
|
||||
// Replace with the actual method you use to check file existence
|
||||
await smartfile.fs.fileExists(
|
||||
path.join(testAssetsPath, 'temp', 'mytest.json'),
|
||||
),
|
||||
).toBeTrue();
|
||||
const targetPath = path.join(testAssetsPath, 'temp', 'stream-mytest.json');
|
||||
await streamFile.writeToDisk(targetPath);
|
||||
|
||||
// Verify the file was written by reading it back
|
||||
const verifyFile = await factory.fromFilePath(targetPath);
|
||||
expect(verifyFile.contentBuffer).toBeInstanceOf(Buffer);
|
||||
});
|
||||
|
||||
tap.test('StreamFile should write to a directory', async () => {
|
||||
const streamFile = await smartfile.StreamFile.fromPath(
|
||||
tap.test('StreamFile -> should write to a directory', async () => {
|
||||
const streamFile = await factory.streamFromPath(
|
||||
path.join(testAssetsPath, 'mytest.json'),
|
||||
);
|
||||
// Set relative path so writeToDir knows where to put it
|
||||
streamFile.relativeFilePath = 'mytest-fromdir.json';
|
||||
await streamFile.writeToDir(path.join(testAssetsPath, 'temp'));
|
||||
|
||||
// Verify the file was written
|
||||
expect(
|
||||
await smartfile.fs.fileExists(
|
||||
path.join(testAssetsPath, 'temp', 'mytest.json'),
|
||||
),
|
||||
).toBeTrue();
|
||||
const targetPath = path.join(testAssetsPath, 'temp', 'mytest-fromdir.json');
|
||||
const verifyFile = await factory.fromFilePath(targetPath);
|
||||
expect(verifyFile.contentBuffer).toBeInstanceOf(Buffer);
|
||||
});
|
||||
|
||||
tap.test('StreamFile should return content as a buffer', async () => {
|
||||
const streamFile = await smartfile.StreamFile.fromPath(
|
||||
tap.test('StreamFile -> should return content as a buffer', async () => {
|
||||
const streamFile = await factory.streamFromPath(
|
||||
path.join(testAssetsPath, 'mytest.json'),
|
||||
);
|
||||
const contentBuffer = await streamFile.getContentAsBuffer();
|
||||
expect(contentBuffer).toBeInstanceOf(Buffer);
|
||||
// Further checks on the content can be added here if necessary
|
||||
});
|
||||
|
||||
tap.test('StreamFile should return content as a string', async () => {
|
||||
const streamFile = await smartfile.StreamFile.fromPath(
|
||||
tap.test('StreamFile -> should return content as a string', async () => {
|
||||
const streamFile = await factory.streamFromPath(
|
||||
path.join(testAssetsPath, 'mytest.json'),
|
||||
);
|
||||
const contentString = await streamFile.getContentAsString();
|
||||
expect(contentString).toBeTypeofString();
|
||||
|
||||
// Verify the content matches what's expected
|
||||
// This assumes the file contains a JSON object with a key 'key1' with value 'this works'
|
||||
expect(JSON.parse(contentString).key1).toEqual('this works');
|
||||
const parsed = JSON.parse(contentString);
|
||||
expect(parsed.key1).toEqual('this works');
|
||||
});
|
||||
|
||||
tap.test('StreamFile -> should get size', async () => {
|
||||
const buffer = Buffer.from('test content for size');
|
||||
const streamFile = factory.streamFromBuffer(buffer, 'sizefile.txt');
|
||||
const size = await streamFile.getSize();
|
||||
expect(size).toEqual(buffer.length);
|
||||
});
|
||||
|
||||
tap.test('StreamFile -> should handle multi-use streams', async () => {
|
||||
const buffer = Buffer.from('multi-use content');
|
||||
const streamFile = factory.streamFromBuffer(buffer, 'multiuse.txt');
|
||||
streamFile.multiUse = true;
|
||||
|
||||
// Read multiple times
|
||||
const content1 = await streamFile.getContentAsString();
|
||||
const content2 = await streamFile.getContentAsString();
|
||||
|
||||
expect(content1).toEqual('multi-use content');
|
||||
expect(content2).toEqual('multi-use content');
|
||||
});
|
||||
|
||||
tap.test('StreamFile -> should convert to SmartFile', async () => {
|
||||
const buffer = Buffer.from('convert to smartfile');
|
||||
const streamFile = factory.streamFromBuffer(buffer, 'convert.txt');
|
||||
|
||||
const smartFile = await streamFile.toSmartFile();
|
||||
expect(smartFile).toBeInstanceOf(smartfile.SmartFile);
|
||||
expect(smartFile.parseContentAsString()).toEqual('convert to smartfile');
|
||||
});
|
||||
|
||||
tap.test('StreamFile -> should create readable stream', async () => {
|
||||
const buffer = Buffer.from('readable stream content');
|
||||
const streamFile = factory.streamFromBuffer(buffer, 'readable.txt');
|
||||
|
||||
const stream = await streamFile.createReadStream();
|
||||
expect(stream).toHaveProperty('pipe');
|
||||
|
||||
// Read from stream
|
||||
const chunks: Buffer[] = [];
|
||||
stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
|
||||
|
||||
await new Promise((resolve) => {
|
||||
stream.on('end', resolve);
|
||||
});
|
||||
|
||||
const content = Buffer.concat(chunks).toString();
|
||||
expect(content).toEqual('readable stream content');
|
||||
});
|
||||
|
||||
// Start the test sequence
|
||||
|
||||
433
test/test.ts
433
test/test.ts
@@ -1,353 +1,142 @@
|
||||
import * as smartfile from '../ts/index.js';
|
||||
import * as path from 'path';
|
||||
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { MockSmartFs } from './helpers/mock-smartfs.js';
|
||||
|
||||
// Create factory with MockSmartFs
|
||||
const mockFs = new MockSmartFs();
|
||||
const factory = new smartfile.SmartFileFactory(mockFs);
|
||||
|
||||
// ---------------------------
|
||||
// smartfile.fs
|
||||
// SmartFileFactory Tests
|
||||
// ---------------------------
|
||||
|
||||
tap.test(
|
||||
'.fs.fileExistsSync -> should return an accurate boolean',
|
||||
async () => {
|
||||
// tslint:disable-next-line: no-unused-expression
|
||||
expect(
|
||||
smartfile.fs.fileExistsSync('./test/testassets/mytest.json'),
|
||||
).toBeTrue();
|
||||
// tslint:disable-next-line: no-unused-expression
|
||||
expect(
|
||||
smartfile.fs.fileExistsSync('./test/testassets/notthere.json'),
|
||||
).toBeFalse();
|
||||
},
|
||||
);
|
||||
|
||||
tap.test('.fs.fileExists -> should resolve or reject a promise', async () => {
|
||||
await expect(
|
||||
smartfile.fs.fileExists('./test/testassets/mytest.json'),
|
||||
).resolves.toBeTrue();
|
||||
await expect(
|
||||
smartfile.fs.fileExists('./test/testassets/notthere.json'),
|
||||
).resolves.toBeFalse();
|
||||
tap.test('SmartFileFactory.nodeFs() -> should create a default factory', async () => {
|
||||
const defaultFactory = smartfile.SmartFileFactory.nodeFs();
|
||||
expect(defaultFactory).toBeInstanceOf(smartfile.SmartFileFactory);
|
||||
});
|
||||
|
||||
tap.test(
|
||||
'.fs.listFoldersSync() -> should get the file type from a string',
|
||||
async () => {
|
||||
expect(smartfile.fs.listFoldersSync('./test/testassets/')).toContain(
|
||||
'testfolder',
|
||||
);
|
||||
expect(smartfile.fs.listFoldersSync('./test/testassets/')).not.toContain(
|
||||
'notExistentFolder',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
tap.test(
|
||||
'.fs.listFolders() -> should get the file type from a string',
|
||||
async () => {
|
||||
const folderArrayArg = await smartfile.fs.listFolders('./test/testassets/');
|
||||
expect(folderArrayArg).toContain('testfolder');
|
||||
expect(folderArrayArg).not.toContain('notExistentFolder');
|
||||
},
|
||||
);
|
||||
|
||||
tap.test(
|
||||
'.fs.listFilesSync() -> should get the file type from a string',
|
||||
async () => {
|
||||
expect(smartfile.fs.listFilesSync('./test/testassets/')).toContain(
|
||||
'mytest.json',
|
||||
);
|
||||
expect(smartfile.fs.listFilesSync('./test/testassets/')).not.toContain(
|
||||
'notExistentFile',
|
||||
);
|
||||
expect(
|
||||
smartfile.fs.listFilesSync('./test/testassets/', /mytest\.json/),
|
||||
).toContain('mytest.json');
|
||||
expect(
|
||||
smartfile.fs.listFilesSync('./test/testassets/', /mytests.json/),
|
||||
).not.toContain('mytest.json');
|
||||
},
|
||||
);
|
||||
|
||||
tap.test(
|
||||
'.fs.listFiles() -> should get the file type from a string',
|
||||
async () => {
|
||||
const folderArrayArg = await smartfile.fs.listFiles('./test/testassets/');
|
||||
expect(folderArrayArg).toContain('mytest.json');
|
||||
expect(folderArrayArg).not.toContain('notExistentFile');
|
||||
},
|
||||
);
|
||||
|
||||
tap.test('.fs.listFileTree() -> should get a file tree', async () => {
|
||||
const folderArrayArg = await smartfile.fs.listFileTree(
|
||||
path.resolve('./test/testassets/'),
|
||||
'**/*.txt',
|
||||
);
|
||||
expect(folderArrayArg).toContain('testfolder/testfile1.txt');
|
||||
expect(folderArrayArg).not.toContain('mytest.json');
|
||||
tap.test('SmartFileFactory.fromFilePath() -> should create a SmartFile from file path', async () => {
|
||||
const smartFile = await factory.fromFilePath('./test/testassets/mytest.json', process.cwd());
|
||||
expect(smartFile).toBeInstanceOf(smartfile.SmartFile);
|
||||
expect(smartFile.path).toEqual('test/testassets/mytest.json');
|
||||
expect(smartFile.contentBuffer).toBeInstanceOf(Buffer);
|
||||
});
|
||||
|
||||
tap.test(
|
||||
'.fs.listFileTree() -> should find both root and nested .ts files with **/*.ts pattern',
|
||||
async () => {
|
||||
const tsFiles = await smartfile.fs.listFileTree(process.cwd(), '**/*.ts');
|
||||
// Should find both root-level and nested TypeScript files
|
||||
expect(tsFiles).toContain('ts/index.ts');
|
||||
expect(tsFiles).toContain('ts/classes.smartfile.ts');
|
||||
expect(tsFiles).toContain('test/test.ts');
|
||||
// Should find files in multiple levels of nesting
|
||||
expect(tsFiles.filter((f) => f.endsWith('.ts')).length).toBeGreaterThan(5);
|
||||
// Verify it finds files at all levels (root 'ts/' and nested 'test/')
|
||||
const hasRootLevelTs = tsFiles.some(
|
||||
(f) => f.startsWith('ts/') && f.endsWith('.ts'),
|
||||
);
|
||||
const hasNestedTs = tsFiles.some(
|
||||
(f) => f.startsWith('test/') && f.endsWith('.ts'),
|
||||
);
|
||||
expect(hasRootLevelTs).toBeTrue();
|
||||
expect(hasNestedTs).toBeTrue();
|
||||
},
|
||||
);
|
||||
|
||||
tap.test(
|
||||
'.fs.listFileTree() -> should handle edge cases with **/ patterns consistently',
|
||||
async () => {
|
||||
// Test that our fix ensures no duplicate files in results
|
||||
const jsonFiles = await smartfile.fs.listFileTree(
|
||||
path.resolve('./test/testassets/'),
|
||||
'**/*.json',
|
||||
);
|
||||
const uniqueFiles = [...new Set(jsonFiles)];
|
||||
expect(jsonFiles.length).toEqual(uniqueFiles.length);
|
||||
|
||||
// Test that it finds root level files with **/ patterns
|
||||
const txtFiles = await smartfile.fs.listFileTree(
|
||||
path.resolve('./test/testassets/'),
|
||||
'**/*.txt',
|
||||
);
|
||||
// Should include both direct files and nested files
|
||||
expect(txtFiles).toContain('mytest.txt');
|
||||
expect(txtFiles).toContain('testfolder/testfile1.txt');
|
||||
},
|
||||
);
|
||||
|
||||
tap.test(
|
||||
'.fs.fileTreeToObject -> should read a file tree into an Object',
|
||||
async () => {
|
||||
const fileArrayArg = await smartfile.fs.fileTreeToObject(
|
||||
path.resolve('./test/testassets/'),
|
||||
'**/*.txt',
|
||||
);
|
||||
expect(fileArrayArg[0]).toBeInstanceOf(smartfile.SmartFile);
|
||||
expect(fileArrayArg[0].contents.toString()).toEqual(
|
||||
fileArrayArg[0].contentBuffer.toString(),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
tap.test('.fs.copy() -> should copy a directory', async () => {
|
||||
await smartfile.fs.copy(
|
||||
'./test/testassets/testfolder/',
|
||||
'./test/testassets/temp/',
|
||||
);
|
||||
tap.test('SmartFileFactory.fromBuffer() -> should create a SmartFile from buffer', async () => {
|
||||
const buffer = Buffer.from('test content');
|
||||
const smartFile = factory.fromBuffer('./test.txt', buffer);
|
||||
expect(smartFile).toBeInstanceOf(smartfile.SmartFile);
|
||||
expect(smartFile.contentBuffer.toString()).toEqual('test content');
|
||||
});
|
||||
|
||||
tap.test('.fs.copy() -> should copy a file', async () => {
|
||||
await smartfile.fs.copy(
|
||||
'./test/testassets/mytest.yaml',
|
||||
'./test/testassets/temp/mytest.yaml',
|
||||
);
|
||||
tap.test('SmartFileFactory.fromString() -> should create a SmartFile from string', async () => {
|
||||
const smartFile = factory.fromString('./test.txt', 'test content');
|
||||
expect(smartFile).toBeInstanceOf(smartfile.SmartFile);
|
||||
expect(smartFile.parseContentAsString()).toEqual('test content');
|
||||
});
|
||||
|
||||
tap.test('.fs.copy() -> should copy a file and rename it', async () => {
|
||||
await smartfile.fs.copy(
|
||||
'./test/testassets/mytest.yaml',
|
||||
'./test/testassets/temp/mytestRenamed.yaml',
|
||||
);
|
||||
});
|
||||
|
||||
tap.test('.fs.remove() -> should remove an entire directory', async () => {});
|
||||
|
||||
tap.test('.fs.remove -> should remove single files', async () => {
|
||||
await smartfile.fs.remove('./test/testassets/temp/mytestRenamed.yaml');
|
||||
});
|
||||
|
||||
tap.test(
|
||||
'.fs.removeSync -> should remove single files synchronouly',
|
||||
async () => {
|
||||
smartfile.fs.removeSync('./test/testassets/temp/testfile1.txt');
|
||||
expect(
|
||||
smartfile.fs.fileExistsSync('./test/testassets/temp/testfile1.txt'),
|
||||
).toBeFalse();
|
||||
},
|
||||
);
|
||||
|
||||
tap.test('.fs.removeMany -> should remove and array of files', async () => {
|
||||
smartfile.fs
|
||||
.removeMany([
|
||||
'./test/testassets/temp/testfile1.txt',
|
||||
'./test/testassets/temp/testfile2.txt',
|
||||
])
|
||||
.then(() => {
|
||||
expect(
|
||||
smartfile.fs.fileExistsSync('./test/testassets/temp/testfile1.txt'),
|
||||
).toBeFalse();
|
||||
expect(
|
||||
smartfile.fs.fileExistsSync('./test/testassets/temp/testfile2.txt'),
|
||||
).toBeFalse();
|
||||
});
|
||||
});
|
||||
|
||||
tap.test(
|
||||
'.fs.removeManySync -> should remove and array of single files synchronouly',
|
||||
async () => {
|
||||
smartfile.fs.removeManySync([
|
||||
'./test/testassets/temp/testfile1.txt',
|
||||
'./test/testassets/temp/testfile2.txt',
|
||||
]);
|
||||
expect(
|
||||
smartfile.fs.fileExistsSync('./test/testassets/temp/testfile1.txt'),
|
||||
).toBeFalse();
|
||||
expect(
|
||||
smartfile.fs.fileExistsSync('./test/testassets/temp/testfile2.txt'),
|
||||
).toBeFalse();
|
||||
},
|
||||
);
|
||||
|
||||
tap.test(
|
||||
'.fs.toObjectSync() -> should read an .yaml file to an object',
|
||||
async () => {
|
||||
const testData = smartfile.fs.toObjectSync('./test/testassets/mytest.yaml');
|
||||
expect(testData.key1).toEqual('this works');
|
||||
expect(testData.key2).toEqual('this works too');
|
||||
},
|
||||
);
|
||||
tap.test(
|
||||
'.fs.toObjectSync() -> should state unknown file type for unknown file types',
|
||||
async () => {
|
||||
const testData = smartfile.fs.toObjectSync('./test/testassets/mytest.txt');
|
||||
},
|
||||
);
|
||||
|
||||
tap.test(
|
||||
'.fs.toObjectSync() -> should read an .json file to an object',
|
||||
async () => {
|
||||
const testData = smartfile.fs.toObjectSync('./test/testassets/mytest.json');
|
||||
expect(testData.key1).toEqual('this works');
|
||||
expect(testData.key2).toEqual('this works too');
|
||||
},
|
||||
);
|
||||
|
||||
tap.test('.fs.toStringSync() -> should read a file to a string', async () => {
|
||||
expect(smartfile.fs.toStringSync('./test/testassets/mytest.txt')).toEqual(
|
||||
'Some TestString &&%$',
|
||||
);
|
||||
tap.test('SmartFileFactory.fromUrl() -> should create a SmartFile from URL', async () => {
|
||||
// Note: This test would need a real HTTP endpoint or mock
|
||||
// For now, we'll skip it or test with a known URL
|
||||
// const smartFile = await factory.fromUrl('https://example.com/test.json');
|
||||
// expect(smartFile).toBeInstanceOf(smartfile.SmartFile);
|
||||
});
|
||||
|
||||
// ---------------------------
|
||||
// smartfile.interpreter
|
||||
// SmartFile Instance Tests
|
||||
// ---------------------------
|
||||
|
||||
tap.test(
|
||||
'.interpreter.filetype() -> should get the file type from a string',
|
||||
async () => {
|
||||
expect(smartfile.interpreter.filetype('./somefolder/data.json')).toEqual(
|
||||
'json',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// ---------------------------
|
||||
// smartfile.memory
|
||||
// ---------------------------
|
||||
|
||||
tap.test(
|
||||
'.memory.toFs() -> should write a file to disk and return a promise',
|
||||
async () => {
|
||||
const localString = 'myString';
|
||||
await smartfile.memory.toFs(
|
||||
localString,
|
||||
path.join(process.cwd(), './test/testassets/temp/testMemToFs.txt'),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
tap.test(
|
||||
'.memory.toFsSync() -> should write a file to disk and return true if successfull',
|
||||
async () => {
|
||||
const localString = 'myString';
|
||||
smartfile.memory.toFsSync(
|
||||
localString,
|
||||
path.join(process.cwd(), './test/testassets/temp/testMemToFsSync.txt'),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// ---------------------------
|
||||
// smartfile.Smartfile
|
||||
// ---------------------------
|
||||
|
||||
tap.test('.Smartfile -> should produce vinyl compatible files', async () => {
|
||||
const smartfileArray = await smartfile.fs.fileTreeToObject(
|
||||
process.cwd(),
|
||||
'./test/testassets/testfolder/**/*',
|
||||
);
|
||||
const localSmartfile = smartfileArray[0];
|
||||
expect(localSmartfile).toBeInstanceOf(smartfile.SmartFile);
|
||||
expect(localSmartfile.contents).toBeInstanceOf(Buffer);
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
expect(localSmartfile.isBuffer()).toBeTrue();
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
expect(localSmartfile.isDirectory()).toBeFalse();
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
expect(localSmartfile.isNull()).toBeFalse();
|
||||
tap.test('SmartFile -> should produce vinyl compatible files', async () => {
|
||||
const smartFile = await factory.fromFilePath('./test/testassets/mytest.json');
|
||||
expect(smartFile).toBeInstanceOf(smartfile.SmartFile);
|
||||
expect(smartFile.contents).toBeInstanceOf(Buffer);
|
||||
expect(smartFile.isBuffer()).toBeTrue();
|
||||
expect(smartFile.isDirectory()).toBeFalse();
|
||||
expect(smartFile.isNull()).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('should output a smartfile array to disk', async () => {
|
||||
const smartfileArray = await smartfile.fs.fileTreeToObject(
|
||||
'./test/testassets/testfolder/',
|
||||
'*',
|
||||
);
|
||||
for (const smartfileInstance of smartfileArray) {
|
||||
console.log(smartfileInstance.relative);
|
||||
console.log(smartfileInstance.path);
|
||||
console.log(smartfileInstance.base);
|
||||
console.log(smartfileInstance.parsedPath);
|
||||
}
|
||||
await smartfile.memory.smartfileArrayToFs(
|
||||
smartfileArray,
|
||||
path.resolve('./test/testassets/temp/testoutput/'),
|
||||
);
|
||||
});
|
||||
|
||||
tap.test('should create, store and retrieve valid smartfiles', async () => {
|
||||
tap.test('SmartFile -> should write to disk', async () => {
|
||||
const fileString = 'hi there';
|
||||
const filePath = './test/testassets/utf8.txt';
|
||||
const smartfileInstance = await smartfile.SmartFile.fromString(
|
||||
filePath,
|
||||
fileString,
|
||||
'utf8',
|
||||
);
|
||||
smartfileInstance.write();
|
||||
const smartfileInstance2 = await smartfile.SmartFile.fromFilePath(filePath);
|
||||
const retrievedString = smartfileInstance.contents.toString();
|
||||
const filePath = './test/testassets/temp/utf8.txt';
|
||||
const smartFile = factory.fromString(filePath, fileString, 'utf8');
|
||||
await smartFile.writeToDiskAtPath(filePath);
|
||||
|
||||
// Read it back
|
||||
const smartFile2 = await factory.fromFilePath(filePath);
|
||||
const retrievedString = smartFile2.parseContentAsString();
|
||||
expect(retrievedString).toEqual(fileString);
|
||||
});
|
||||
|
||||
tap.test('should get a hash', async () => {
|
||||
tap.test('SmartFile -> should get a hash', async () => {
|
||||
const fileString = 'hi there';
|
||||
const filePath = './test/testassets/utf8.txt';
|
||||
const smartfileInstance = await smartfile.SmartFile.fromString(
|
||||
filePath,
|
||||
fileString,
|
||||
'utf8',
|
||||
);
|
||||
const hash = await smartfileInstance.getHash();
|
||||
console.log(hash);
|
||||
const smartFile = factory.fromString('./test/testassets/utf8.txt', fileString, 'utf8');
|
||||
const hash = await smartFile.getHash();
|
||||
expect(hash).toBeTypeofString();
|
||||
expect(hash.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
tap.test('should wait for file to be ready', async () => {
|
||||
await smartfile.fs.waitForFileToBeReady('./test/testassets/mytest.json');
|
||||
tap.test('SmartFile -> should update file name', async () => {
|
||||
const smartFile = factory.fromString('./test/oldname.txt', 'content');
|
||||
smartFile.updateFileName('newname.txt');
|
||||
expect(smartFile.parsedPath.base).toEqual('newname.txt');
|
||||
});
|
||||
|
||||
tap.test('SmartFile -> should edit content as string', async () => {
|
||||
const smartFile = factory.fromString('./test.txt', 'original content');
|
||||
await smartFile.editContentAsString(async (content) => {
|
||||
return content.replace('original', 'modified');
|
||||
});
|
||||
expect(smartFile.parseContentAsString()).toEqual('modified content');
|
||||
});
|
||||
|
||||
tap.test('SmartFile -> should get stream', async () => {
|
||||
const smartFile = factory.fromString('./test.txt', 'stream content');
|
||||
const stream = smartFile.getStream();
|
||||
expect(stream).toHaveProperty('pipe');
|
||||
|
||||
// Read from stream
|
||||
const chunks: Buffer[] = [];
|
||||
stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
|
||||
|
||||
await new Promise((resolve) => {
|
||||
stream.on('end', resolve);
|
||||
});
|
||||
|
||||
const content = Buffer.concat(chunks).toString();
|
||||
expect(content).toEqual('stream content');
|
||||
});
|
||||
|
||||
tap.test('SmartFile -> should get size', async () => {
|
||||
const content = 'test content with some length';
|
||||
const smartFile = factory.fromString('./test.txt', content);
|
||||
const size = await smartFile.getSize();
|
||||
expect(size).toEqual(Buffer.from(content).length);
|
||||
});
|
||||
|
||||
tap.test('SmartFile -> should parse content as buffer', async () => {
|
||||
const buffer = Buffer.from('buffer content');
|
||||
const smartFile = factory.fromBuffer('./test.txt', buffer);
|
||||
const parsedBuffer = smartFile.parseContentAsBuffer();
|
||||
expect(parsedBuffer).toBeInstanceOf(Buffer);
|
||||
expect(parsedBuffer.toString()).toEqual('buffer content');
|
||||
});
|
||||
|
||||
tap.test('SmartFile -> should write to directory', async () => {
|
||||
const smartFile = factory.fromString('subdir/test.txt', 'directory test content');
|
||||
const writtenPath = await smartFile.writeToDir('./test/testassets/temp');
|
||||
expect(writtenPath).toContain('subdir/test.txt');
|
||||
});
|
||||
|
||||
tap.test('SmartFile -> should get parsed path', async () => {
|
||||
const smartFile = factory.fromString('./path/to/file.txt', 'content');
|
||||
expect(smartFile.parsedPath.base).toEqual('file.txt');
|
||||
expect(smartFile.parsedPath.ext).toEqual('.txt');
|
||||
expect(smartFile.parsedPath.name).toEqual('file');
|
||||
});
|
||||
|
||||
tap.test('SmartFile -> should get absolute path', async () => {
|
||||
const smartFile = factory.fromString('relative/path.txt', 'content', 'utf8', '/base');
|
||||
expect(smartFile.absolutePath).toEqual('/base/relative/path.txt');
|
||||
});
|
||||
|
||||
tap.start();
|
||||
|
||||
@@ -1,19 +1,235 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
|
||||
import * as smartfile from '../ts/index.js';
|
||||
import { MockSmartFs } from './helpers/mock-smartfs.js';
|
||||
|
||||
tap.test('should create a virtualdirectory', async () => {
|
||||
const virtualDir = await smartfile.VirtualDirectory.fromFsDirPath(
|
||||
'./test/testassets/testfolder',
|
||||
);
|
||||
// Create factory with MockSmartFs
|
||||
const mockFs = new MockSmartFs();
|
||||
const factory = new smartfile.SmartFileFactory(mockFs);
|
||||
|
||||
// ---------------------------
|
||||
// VirtualDirectory Factory Tests
|
||||
// ---------------------------
|
||||
|
||||
tap.test('SmartFileFactory.virtualDirectoryFromPath() -> should create a VirtualDirectory from fs path', async () => {
|
||||
const virtualDir = await factory.virtualDirectoryFromPath('./test/testassets/testfolder');
|
||||
expect(virtualDir).toBeInstanceOf(smartfile.VirtualDirectory);
|
||||
expect(virtualDir.smartfileArray.length).toEqual(4);
|
||||
});
|
||||
|
||||
tap.test('should write to a directory', async () => {
|
||||
const virtualDir = await smartfile.VirtualDirectory.fromFsDirPath(
|
||||
'./test/testassets/testfolder',
|
||||
);
|
||||
virtualDir.saveToDisk('./test/testassets/test');
|
||||
tap.test('SmartFileFactory.virtualDirectoryEmpty() -> should create an empty VirtualDirectory', async () => {
|
||||
const virtualDir = factory.virtualDirectoryEmpty();
|
||||
expect(virtualDir).toBeInstanceOf(smartfile.VirtualDirectory);
|
||||
expect(virtualDir.isEmpty()).toBeTrue();
|
||||
expect(virtualDir.size()).toEqual(0);
|
||||
});
|
||||
|
||||
tap.test('SmartFileFactory.virtualDirectoryFromFileArray() -> should create VirtualDirectory from files', async () => {
|
||||
const file1 = factory.fromString('file1.txt', 'content1');
|
||||
const file2 = factory.fromString('file2.txt', 'content2');
|
||||
|
||||
const virtualDir = factory.virtualDirectoryFromFileArray([file1, file2]);
|
||||
expect(virtualDir).toBeInstanceOf(smartfile.VirtualDirectory);
|
||||
expect(virtualDir.size()).toEqual(2);
|
||||
});
|
||||
|
||||
// ---------------------------
|
||||
// VirtualDirectory Collection Methods
|
||||
// ---------------------------
|
||||
|
||||
tap.test('VirtualDirectory -> should add and list files', async () => {
|
||||
const virtualDir = factory.virtualDirectoryEmpty();
|
||||
const file1 = factory.fromString('test1.txt', 'content1');
|
||||
const file2 = factory.fromString('test2.txt', 'content2');
|
||||
|
||||
virtualDir.addSmartfile(file1);
|
||||
virtualDir.addSmartfile(file2);
|
||||
|
||||
const files = virtualDir.listFiles();
|
||||
expect(files.length).toEqual(2);
|
||||
expect(files[0].path).toEqual('test1.txt');
|
||||
expect(files[1].path).toEqual('test2.txt');
|
||||
});
|
||||
|
||||
tap.test('VirtualDirectory -> should check file existence', async () => {
|
||||
const virtualDir = factory.virtualDirectoryEmpty();
|
||||
const file = factory.fromString('exists.txt', 'content');
|
||||
virtualDir.addSmartfile(file);
|
||||
|
||||
expect(virtualDir.exists('exists.txt')).toBeTrue();
|
||||
expect(virtualDir.has('exists.txt')).toBeTrue();
|
||||
expect(virtualDir.exists('not-there.txt')).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('VirtualDirectory -> should get file by path', async () => {
|
||||
const virtualDir = factory.virtualDirectoryEmpty();
|
||||
const file = factory.fromString('getme.txt', 'my content');
|
||||
virtualDir.addSmartfile(file);
|
||||
|
||||
const retrieved = await virtualDir.getFileByPath('getme.txt');
|
||||
expect(retrieved).not.toBeUndefined();
|
||||
expect(retrieved!.parseContentAsString()).toEqual('my content');
|
||||
});
|
||||
|
||||
tap.test('VirtualDirectory -> should remove file by path', async () => {
|
||||
const virtualDir = factory.virtualDirectoryEmpty();
|
||||
const file = factory.fromString('remove.txt', 'content');
|
||||
virtualDir.addSmartfile(file);
|
||||
|
||||
expect(virtualDir.exists('remove.txt')).toBeTrue();
|
||||
|
||||
const removed = virtualDir.removeByPath('remove.txt');
|
||||
expect(removed).toBeTrue();
|
||||
expect(virtualDir.exists('remove.txt')).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('VirtualDirectory -> should clear all files', async () => {
|
||||
const virtualDir = factory.virtualDirectoryEmpty();
|
||||
virtualDir.addSmartfile(factory.fromString('file1.txt', 'content1'));
|
||||
virtualDir.addSmartfile(factory.fromString('file2.txt', 'content2'));
|
||||
|
||||
expect(virtualDir.size()).toEqual(2);
|
||||
|
||||
virtualDir.clear();
|
||||
|
||||
expect(virtualDir.size()).toEqual(0);
|
||||
expect(virtualDir.isEmpty()).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('VirtualDirectory -> should merge with another VirtualDirectory', async () => {
|
||||
const vdir1 = factory.virtualDirectoryEmpty();
|
||||
vdir1.addSmartfile(factory.fromString('file1.txt', 'content1'));
|
||||
|
||||
const vdir2 = factory.virtualDirectoryEmpty();
|
||||
vdir2.addSmartfile(factory.fromString('file2.txt', 'content2'));
|
||||
|
||||
vdir1.merge(vdir2);
|
||||
|
||||
expect(vdir1.size()).toEqual(2);
|
||||
expect(vdir1.exists('file1.txt')).toBeTrue();
|
||||
expect(vdir1.exists('file2.txt')).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('VirtualDirectory -> should filter files', async () => {
|
||||
const virtualDir = factory.virtualDirectoryEmpty();
|
||||
virtualDir.addSmartfile(factory.fromString('file1.txt', 'content1'));
|
||||
virtualDir.addSmartfile(factory.fromString('file2.md', 'content2'));
|
||||
virtualDir.addSmartfile(factory.fromString('file3.txt', 'content3'));
|
||||
|
||||
const filtered = virtualDir.filter(file => file.path.endsWith('.txt'));
|
||||
|
||||
expect(filtered.size()).toEqual(2);
|
||||
expect(filtered.exists('file1.txt')).toBeTrue();
|
||||
expect(filtered.exists('file3.txt')).toBeTrue();
|
||||
expect(filtered.exists('file2.md')).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('VirtualDirectory -> should map files', async () => {
|
||||
const virtualDir = factory.virtualDirectoryEmpty();
|
||||
virtualDir.addSmartfile(factory.fromString('file1.txt', 'content1'));
|
||||
virtualDir.addSmartfile(factory.fromString('file2.txt', 'content2'));
|
||||
|
||||
const mapped = virtualDir.map(file => {
|
||||
file.setContentsFromString(file.parseContentAsString().toUpperCase());
|
||||
return file;
|
||||
});
|
||||
|
||||
const files = mapped.listFiles();
|
||||
expect(files[0].parseContentAsString()).toEqual('CONTENT1');
|
||||
expect(files[1].parseContentAsString()).toEqual('CONTENT2');
|
||||
});
|
||||
|
||||
tap.test('VirtualDirectory -> should find files', async () => {
|
||||
const virtualDir = factory.virtualDirectoryEmpty();
|
||||
virtualDir.addSmartfile(factory.fromString('find.txt', 'findme'));
|
||||
virtualDir.addSmartfile(factory.fromString('other.txt', 'other'));
|
||||
|
||||
const found = virtualDir.find(file => file.parseContentAsString() === 'findme');
|
||||
|
||||
expect(found).not.toBeUndefined();
|
||||
expect(found!.path).toEqual('find.txt');
|
||||
});
|
||||
|
||||
tap.test('VirtualDirectory -> should list directories', async () => {
|
||||
const virtualDir = factory.virtualDirectoryEmpty();
|
||||
virtualDir.addSmartfile(factory.fromString('dir1/file1.txt', 'content1'));
|
||||
virtualDir.addSmartfile(factory.fromString('dir1/file2.txt', 'content2'));
|
||||
virtualDir.addSmartfile(factory.fromString('dir2/file3.txt', 'content3'));
|
||||
virtualDir.addSmartfile(factory.fromString('root.txt', 'content4'));
|
||||
|
||||
const dirs = virtualDir.listDirectories();
|
||||
|
||||
expect(dirs).toContain('dir1');
|
||||
expect(dirs).toContain('dir2');
|
||||
expect(dirs.length).toEqual(2);
|
||||
});
|
||||
|
||||
tap.test('VirtualDirectory -> should save to disk', async () => {
|
||||
const virtualDir = factory.virtualDirectoryEmpty();
|
||||
virtualDir.addSmartfile(factory.fromString('saved1.txt', 'saved content 1'));
|
||||
virtualDir.addSmartfile(factory.fromString('subdir/saved2.txt', 'saved content 2'));
|
||||
|
||||
await virtualDir.saveToDisk('./test/testassets/temp/vdir-output');
|
||||
|
||||
// Verify files were written
|
||||
const file1 = await factory.fromFilePath('./test/testassets/temp/vdir-output/saved1.txt');
|
||||
expect(file1.parseContentAsString()).toEqual('saved content 1');
|
||||
|
||||
const file2 = await factory.fromFilePath('./test/testassets/temp/vdir-output/subdir/saved2.txt');
|
||||
expect(file2.parseContentAsString()).toEqual('saved content 2');
|
||||
});
|
||||
|
||||
tap.test('VirtualDirectory -> should convert to transferable object', async () => {
|
||||
const virtualDir = factory.virtualDirectoryEmpty();
|
||||
virtualDir.addSmartfile(factory.fromString('trans1.txt', 'transferable1'));
|
||||
virtualDir.addSmartfile(factory.fromString('trans2.txt', 'transferable2'));
|
||||
|
||||
const transferable = await virtualDir.toVirtualDirTransferableObject();
|
||||
|
||||
expect(transferable.files).toBeInstanceOf(Array);
|
||||
expect(transferable.files.length).toEqual(2);
|
||||
});
|
||||
|
||||
// TODO: Fix serialization/deserialization with smartjson
|
||||
// tap.test('VirtualDirectory -> should create from transferable object', async () => {
|
||||
// const originalDir = factory.virtualDirectoryEmpty();
|
||||
// originalDir.addSmartfile(factory.fromString('original.txt', 'original content'));
|
||||
|
||||
// const transferable = await originalDir.toVirtualDirTransferableObject();
|
||||
// const restoredDir = await factory.virtualDirectoryFromTransferable(transferable);
|
||||
|
||||
// expect(restoredDir.size()).toEqual(1);
|
||||
// expect(restoredDir.exists('original.txt')).toBeTrue();
|
||||
|
||||
// const file = await restoredDir.getFileByPath('original.txt');
|
||||
// expect(file!.parseContentAsString()).toEqual('original content');
|
||||
// });
|
||||
|
||||
tap.test('VirtualDirectory -> should shift to subdirectory', async () => {
|
||||
const virtualDir = factory.virtualDirectoryEmpty();
|
||||
virtualDir.addSmartfile(factory.fromString('root/sub/file1.txt', 'content1'));
|
||||
virtualDir.addSmartfile(factory.fromString('root/sub/file2.txt', 'content2'));
|
||||
virtualDir.addSmartfile(factory.fromString('root/other.txt', 'content3'));
|
||||
|
||||
const shifted = await virtualDir.shiftToSubdirectory('root/sub');
|
||||
|
||||
expect(shifted.size()).toEqual(2);
|
||||
expect(shifted.exists('file1.txt')).toBeTrue();
|
||||
expect(shifted.exists('file2.txt')).toBeTrue();
|
||||
expect(shifted.exists('other.txt')).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('VirtualDirectory -> should add another virtual directory with new root', async () => {
|
||||
const vdir1 = factory.virtualDirectoryEmpty();
|
||||
vdir1.addSmartfile(factory.fromString('existing.txt', 'existing'));
|
||||
|
||||
const vdir2 = factory.virtualDirectoryEmpty();
|
||||
vdir2.addSmartfile(factory.fromString('added.txt', 'added'));
|
||||
|
||||
await vdir1.addVirtualDirectory(vdir2, 'newroot');
|
||||
|
||||
expect(vdir1.size()).toEqual(2);
|
||||
expect(vdir1.exists('existing.txt')).toBeTrue();
|
||||
expect(vdir1.exists('newroot/added.txt')).toBeTrue();
|
||||
});
|
||||
|
||||
tap.start();
|
||||
|
||||
7
test/testassets/temp/mytest-fromdir.json
Normal file
7
test/testassets/temp/mytest-fromdir.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"key1": "this works",
|
||||
"key2": "this works too",
|
||||
"key3": {
|
||||
"nestedkey1": "hello"
|
||||
}
|
||||
}
|
||||
7
test/testassets/temp/stream-mytest.json
Normal file
7
test/testassets/temp/stream-mytest.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"key1": "this works",
|
||||
"key2": "this works too",
|
||||
"key3": {
|
||||
"nestedkey1": "hello"
|
||||
}
|
||||
}
|
||||
1
test/testassets/temp/subdir/test.txt
Normal file
1
test/testassets/temp/subdir/test.txt
Normal file
@@ -0,0 +1 @@
|
||||
directory test content
|
||||
1
test/testassets/temp/utf8.txt
Normal file
1
test/testassets/temp/utf8.txt
Normal file
@@ -0,0 +1 @@
|
||||
hi there
|
||||
1
test/testassets/temp/vdir-output/saved1.txt
Normal file
1
test/testassets/temp/vdir-output/saved1.txt
Normal file
@@ -0,0 +1 @@
|
||||
saved content 1
|
||||
1
test/testassets/temp/vdir-output/subdir/saved2.txt
Normal file
1
test/testassets/temp/vdir-output/subdir/saved2.txt
Normal file
@@ -0,0 +1 @@
|
||||
saved content 2
|
||||
Reference in New Issue
Block a user