obsidian-sample-plugin/testHelpers/MockVault.ts

266 lines
6.8 KiB
TypeScript

import {
DataAdapter,
DataWriteOptions,
EventRef,
TAbstractFile,
TFile,
TFolder,
Vault,
} from "obsidian";
import { basename, dirname, join, normalize } from "path";
/**
* Return all files that exist under a given folder.
* @param folder Folder to collect children under.
* @returns All files under this folder, recursively.
*/
const collectChildren = (folder: TFolder): TAbstractFile[] => {
return folder.children.flatMap((f) => {
if (f instanceof TFolder) {
return [f, ...collectChildren(f)];
} else {
return f;
}
});
};
export class MockVault implements Vault {
root: TFolder;
contents: Map<string, string>;
constructor(root: TFolder, contents: Map<string, string>) {
this.root = root;
this.contents = contents;
}
// These aren't implemented in the mock.
adapter: DataAdapter = {} as DataAdapter;
configDir = "";
getName(): string {
return "Mock Vault";
}
getAllLoadedFiles(): TAbstractFile[] {
return [this.root, ...collectChildren(this.root)];
}
getAbstractFileByPath(path: string): TAbstractFile | null {
const normalizedPath = join("/", normalize(path));
return (
this.getAllLoadedFiles().find(
(f) => join("/", normalize(f.path)) === normalizedPath
) || null
);
}
getRoot(): TFolder {
return this.root;
}
async read(file: TFile): Promise<string> {
const p = join("/", file.path);
const contents = this.contents.get(p);
if (!contents) {
throw new Error(`File at path ${p} does not have contents`);
}
return contents;
}
cachedRead(file: TFile): Promise<string> {
return this.read(file);
}
getFiles(): TFile[] {
return this.getAllLoadedFiles().flatMap((f) =>
f instanceof TFile ? f : []
);
}
getMarkdownFiles(): TFile[] {
return this.getFiles().filter(
(f) => f.extension.toLowerCase() === "md"
);
}
private setParent(path: string, f: TAbstractFile) {
const parentPath = dirname(path);
const folder = this.getAbstractFileByPath(parentPath);
if (folder instanceof TFolder) {
f.parent = folder;
folder.children.push(f);
}
throw new Error("Parent path is not folder.");
}
process(
file: TFile,
fn: (data: string) => string,
options?: DataWriteOptions | undefined
): Promise<string> {
throw new Error("Method not implemented.");
}
async create(
path: string,
data: string,
options?: DataWriteOptions | undefined
): Promise<TFile> {
if (this.getAbstractFileByPath(path)) {
throw new Error("File already exists.");
}
const file = new TFile();
file.name = basename(path);
this.setParent(path, file);
this.contents.set(path, data);
return file;
}
async createFolder(path: string): Promise<TFolder> {
const folder = new TFolder();
folder.name = basename(path);
this.setParent(path, folder);
return folder;
}
async delete(
file: TAbstractFile,
force?: boolean | undefined
): Promise<void> {
file.parent?.children.remove(file);
}
trash(file: TAbstractFile, system: boolean): Promise<void> {
return this.delete(file);
}
async rename(file: TAbstractFile, newPath: string): Promise<void> {
const newParentPath = dirname(newPath);
const newParent = this.getAbstractFileByPath(newParentPath);
if (!(newParent instanceof TFolder)) {
throw new Error(`No such folder: ${newParentPath}`);
}
if (file instanceof TFile) {
// If we're renaming a file, just update the parent and name in the
// file, and the entry in the content map.
const contents = this.contents.get(file.path);
if (!contents) {
throw new Error(`File did not have contents: ${file.path}`);
}
this.contents.delete(file.path);
// Update the parent and name and re-set contents with the new path.
// NOTE: This relies on using the included mock that derives the path
// from the parent and filename as a getter property.
file.parent = newParent;
file.name = basename(newPath);
this.contents.set(file.path, contents);
} else if (file instanceof TFolder) {
// If we're renaming a folder, we need to update the content map for
// every TFile under this folder.
// Collect all files under this folder, get the string contents, delete
// the entry for the old path, and return the file and contents in a tuple.
const filesAndContents = collectChildren(file)
.flatMap((f) => (f instanceof TFile ? f : []))
.map((f): [TFile, string] => {
const contents = this.contents.get(f.path);
if (!contents) {
throw new Error(
`File did not have contents: ${f.path}`
);
}
this.contents.delete(f.path);
return [f, contents];
});
// Update the parent and name for this folder.
file.parent = newParent;
file.name = basename(newPath);
// Re-add all the paths to the content dir.
for (const [f, contents] of filesAndContents) {
this.contents.set(f.path, contents);
}
} else {
throw new Error(`File is not a file or folder: ${file.path}`);
}
}
async modify(
file: TFile,
data: string,
options?: DataWriteOptions | undefined
): Promise<void> {
this.contents.set(file.path, data);
}
async copy(file: TFile, newPath: string): Promise<TFile> {
const data = await this.read(file);
return await this.create(newPath, data);
}
// TODO: Implement callbacks.
on(
name: "create",
callback: (file: TAbstractFile) => unknown,
ctx?: unknown
): EventRef;
on(
name: "modify",
callback: (file: TAbstractFile) => unknown,
ctx?: unknown
): EventRef;
on(
name: "delete",
callback: (file: TAbstractFile) => unknown,
ctx?: unknown
): EventRef;
on(
name: "rename",
callback: (file: TAbstractFile, oldPath: string) => unknown,
ctx?: unknown
): EventRef;
on(name: "closed", callback: () => unknown, ctx?: unknown): EventRef;
on(name: unknown, callback: unknown, ctx?: unknown): EventRef {
throw new Error("Method not implemented.");
}
off(name: string, callback: (...data: unknown[]) => unknown): void {
throw new Error("Method not implemented.");
}
offref(ref: EventRef): void {
throw new Error("Method not implemented.");
}
trigger(name: string, ...data: unknown[]): void {
throw new Error("Method not implemented.");
}
tryTrigger(evt: EventRef, args: unknown[]): void {
throw new Error("Method not implemented.");
}
append(
file: TFile,
data: string,
options?: DataWriteOptions | undefined
): Promise<void> {
throw new Error("Method not implemented.");
}
createBinary(
path: string,
data: ArrayBuffer,
options?: DataWriteOptions | undefined
): Promise<TFile> {
throw new Error("Method not implemented.");
}
readBinary(file: TFile): Promise<ArrayBuffer> {
throw new Error("Method not implemented.");
}
modifyBinary(
file: TFile,
data: ArrayBuffer,
options?: DataWriteOptions | undefined
): Promise<void> {
throw new Error("Method not implemented.");
}
getResourcePath(file: TFile): string {
throw new Error("Method not implemented.");
}
}