Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 26 additions & 31 deletions __tests__/fdir.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import { FSLike, fdir } from "../src/index";
import fs from "fs";
import mock from "mock-fs";
import { test, beforeEach, TestContext, vi } from "vitest";
import { test, TestContext, vi } from "vitest";
import path, { sep } from "path";
import { convertSlashes } from "../src/utils";
import { convertSlashes, normalizePath } from "../src/utils";
import picomatch from "picomatch";
import { apiTypes, APITypes, cwd, restricted, root } from "./utils";

beforeEach(() => {
mock.restore();
});
import { MockFS } from "./mock-fs";

test(`crawl single depth directory with callback`, (t) => {
const api = new fdir().withErrors().crawl("__tests__");
Expand Down Expand Up @@ -45,6 +41,7 @@ for (const type of apiTypes) {
includeBasePath: true,
suppressErrors: false,
}).crawl("__tests__");
const files = await api[type]();
t.expect(files.every((file) => file.startsWith("__tests__"))).toBeTruthy();
});

Expand Down Expand Up @@ -130,7 +127,6 @@ for (const type of apiTypes) {

test(`[${type}] crawl but exclude node_modules dir`, async (t) => {
const api = new fdir()
.withErrors()
.withBasePath()
.exclude((dir) => dir.includes("node_modules"))
.crawl(cwd());
Expand All @@ -142,7 +138,6 @@ for (const type of apiTypes) {

test(`[${type}] crawl all files with filter`, async (t) => {
const api = new fdir()
.withErrors()
.withBasePath()
.filter((file) => file.includes(".git"))
.crawl(cwd());
Expand All @@ -152,7 +147,6 @@ for (const type of apiTypes) {

test(`[${type}] crawl all files with multifilter`, async (t) => {
const api = new fdir()
.withErrors()
.withBasePath()
.filter((file) => file.includes(".git"))
.filter((file) => file.includes(".js"))
Expand All @@ -175,7 +169,7 @@ for (const type of apiTypes) {
});

test(`[${type}] get all files in a directory and output full paths (withFullPaths)`, async (t) => {
const api = new fdir().withErrors().withFullPaths().crawl(cwd());
const api = new fdir().withFullPaths().crawl(cwd());
const files = await api[type]();
t.expect(files.every((file) => file.startsWith(root()))).toBeTruthy();
});
Expand All @@ -192,15 +186,9 @@ for (const type of apiTypes) {
});

test(`[${type}] recurse root (files should not contain multiple /)`, async (t) => {
mock({
"/etc": {
hosts: "dooone",
},
});
const api = new fdir().withBasePath().normalize().crawl("/");
const api = new fdir().withBasePath().normalize().crawl("/tmp");
const files = await api[type]();
t.expect(files.every((file) => !file.includes("//"))).toBeTruthy();
mock.restore();
});

test(`[${type}] crawl all files with only counts`, async (t) => {
Expand Down Expand Up @@ -237,7 +225,6 @@ for (const type of apiTypes) {

test(`[${type}] crawl and filter all files and get only counts`, async (t) => {
const api = new fdir()
.withErrors()
.withBasePath()
.filter((file) => file.includes("node_modules"))
.onlyCounts()
Expand Down Expand Up @@ -283,7 +270,7 @@ for (const type of apiTypes) {
});

test(`[${type}] crawl and return relative paths with only dirs`, async (t) => {
mock({
const mockedFs = new MockFS({
"/some/dir/dir1": {
file: "some file",
},
Expand All @@ -294,21 +281,26 @@ for (const type of apiTypes) {
file: "some file",
},
});
await mockedFs.init();
t.onTestFailed(async () => await mockedFs.cleanup());

const api = new fdir({ excludeFiles: true, excludeSymlinks: true })
.withDirs()
const api = new fdir()
.onlyDirs()
.withRelativePaths()
.crawl("/some");
.crawl(mockedFs.resolve("/some"));
const paths = await api[type]();

t.expect(paths.length).toBe(5);
t.expect(paths.filter((p) => p === ".").length).toBe(1);
t.expect(paths.filter((p) => p === "").length).toBe(0);
mock.restore();
t.expect(paths.sort()).toStrictEqual([
".",
"dir/",
"dir/dir1/",
"dir/dir2/",
"dir/dir2/dir3/",
]);
await mockedFs.cleanup();
});

test(`[${type}] crawl and return relative paths with filters and only dirs`, async (t) => {
mock({
const mockedFs = new MockFS({
"/some/dir/dir1": {
file: "some file",
},
Expand All @@ -319,19 +311,22 @@ for (const type of apiTypes) {
file: "some file",
},
});
await mockedFs.init();
t.onTestFailed(async () => await mockedFs.cleanup());

const api = new fdir({ excludeFiles: true, excludeSymlinks: true })
.withDirs()
.withRelativePaths()
.filter((p) => p !== path.join("dir", "dir1/"))
.crawl("/some");
.crawl(mockedFs.resolve("/some"));
const paths = await api[type]();

t.expect(paths.length).toBe(4);
t.expect(paths.includes(path.join("dir", "dir1/"))).toBe(false);
t.expect(paths.filter((p) => p === ".").length).toBe(1);
t.expect(paths.filter((p) => p === "").length).toBe(0);
mock.restore();

await mockedFs.cleanup();
});

test(`[${type}] crawl and return relative paths that end with /`, async (t) => {
Expand Down
100 changes: 100 additions & 0 deletions __tests__/mock-fs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import Directory from "mock-fs/lib/directory";
import FileSystem from "mock-fs/lib/filesystem";
import SymbolicLink from "mock-fs/lib/symlink";
import File from "mock-fs/lib/file";
import { resolve, sep } from "path";
import { root } from "./utils";
import { mkdir, mkdtemp, rm, stat, symlink, writeFile } from "fs/promises";

export class MockFS {
root: string;
constructor(private readonly filesystem: FileSystem.DirectoryItems) {}

async cleanup() {
await rm(this.root, { recursive: true, force: true });
}

normalize(paths: string[]) {
return paths.map((p) => resolvePath(this.root, this.root, p));
}

resolve(path: string) {
return resolvePath(this.root, this.root, path);
}

async init() {
this.root = await mkdtemp("fdir");
const { symlinks } = await this.createFilesystem(
this.root,
this.filesystem
);
for (const { path, targetPath } of symlinks) {
if (!targetPath.includes(this.root))
throw new Error(
"Cannot recurse above the temp directory: " + targetPath
);

const isDirectory = await (async () => {
try {
return (await stat(targetPath)).isDirectory();
} catch {
return false;
}
})();
await symlink(targetPath, path, isDirectory ? "dir" : "file");
}
}

private async createFilesystem(
root: string,
filesystem: FileSystem.DirectoryItems
) {
await mkdir(root, { recursive: true });
let symlinks: { path: string; targetPath: string }[] = [];
for (const name in filesystem) {
const item = filesystem[name];
const parentPath = resolvePath(this.root, root, name);
if (!parentPath.includes(this.root))
throw new Error(
"Cannot recurse above the temp directory: " + parentPath
);
if (typeof item === "function") {
const unknownItem = item();
if (unknownItem instanceof File) {
await writeFile(parentPath, unknownItem.getContent());
} else if (unknownItem instanceof SymbolicLink) {
const targetPath = resolvePath(
this.root,
root,
unknownItem.getPath()
);
symlinks.push({
path: parentPath,
targetPath,
});
} else if (unknownItem instanceof Directory) {
throw new Error("Not implemented.");
}
} else if (typeof item === "string" || Buffer.isBuffer(item)) {
await writeFile(parentPath, item);
} else {
symlinks = [
...symlinks,
...(await this.createFilesystem(parentPath, item)).symlinks,
];
}
}
return { symlinks };
}
}

function resolvePath(rootPath: string, relativeRoot: string, path: string) {
const startsWithRoot = path.startsWith(root());
const endsWithPathSeparator = path.endsWith(sep);
if (startsWithRoot)
return (
resolve(rootPath, path.replace(root(), "")) +
(endsWithPathSeparator ? sep : "")
);
return resolve(relativeRoot, path) + (endsWithPathSeparator ? sep : "");
}
Loading
Loading