@ucdjs/test-utils 1.0.1-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +26 -0
- package/dist/file-tree-CColHWG6.mjs +4436 -0
- package/dist/fs-bridges/index.d.mts +84 -0
- package/dist/fs-bridges/index.mjs +303 -0
- package/dist/helpers-D3XLw9D1.d.mts +1050 -0
- package/dist/index.d.mts +76 -0
- package/dist/index.mjs +89 -0
- package/dist/matchers/types.mjs +149 -0
- package/dist/matchers/vitest-setup.d.mts +1 -0
- package/dist/matchers/vitest-setup.mjs +239 -0
- package/dist/mock-store.d.mts +21 -0
- package/dist/mock-store.mjs +210 -0
- package/dist/msw/vitest-setup.d.mts +1 -0
- package/dist/msw/vitest-setup.mjs +14 -0
- package/dist/msw.d.mts +25 -0
- package/dist/msw.mjs +30 -0
- package/dist/pipelines.d.mts +21 -0
- package/dist/pipelines.mjs +35 -0
- package/package.json +79 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as _ucdjs_fs_bridge0 from "@ucdjs/fs-bridge";
|
|
2
|
+
import { FSEntry, FileSystemBridge } from "@ucdjs/fs-bridge";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
//#region src/fs-bridges/memory-fs-bridge.d.ts
|
|
6
|
+
declare const createMemoryMockFS: _ucdjs_fs_bridge0.FileSystemBridgeFactory<z.ZodOptional<z.ZodObject<{
|
|
7
|
+
basePath: z.ZodOptional<z.ZodString>;
|
|
8
|
+
initialFiles: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
9
|
+
functions: z.ZodOptional<z.ZodObject<{
|
|
10
|
+
read: z.ZodOptional<z.ZodOptional<z.ZodXor<readonly [z.ZodFunction<z.ZodTuple<readonly [z.ZodString], null>, z.ZodPromise<z.ZodString>>, z.ZodLiteral<false>]>>>;
|
|
11
|
+
exists: z.ZodOptional<z.ZodOptional<z.ZodXor<readonly [z.ZodFunction<z.ZodTuple<readonly [z.ZodString], null>, z.ZodPromise<z.ZodBoolean>>, z.ZodLiteral<false>]>>>;
|
|
12
|
+
listdir: z.ZodOptional<z.ZodOptional<z.ZodXor<readonly [z.ZodFunction<z.ZodTuple<readonly [z.ZodString, z.ZodOptional<z.ZodBoolean>], null>, z.ZodPromise<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
13
|
+
name: z.ZodString;
|
|
14
|
+
path: z.ZodString;
|
|
15
|
+
lastModified: z.ZodUnion<[z.ZodNumber, z.ZodNull]>;
|
|
16
|
+
type: z.ZodLiteral<"directory">;
|
|
17
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
18
|
+
name: z.ZodString;
|
|
19
|
+
path: z.ZodString;
|
|
20
|
+
lastModified: z.ZodUnion<[z.ZodNumber, z.ZodNull]>;
|
|
21
|
+
type: z.ZodLiteral<"file">;
|
|
22
|
+
}, z.core.$strip>]>>>>, z.ZodLiteral<false>]>>>;
|
|
23
|
+
write: z.ZodOptional<z.ZodOptional<z.ZodXor<readonly [z.ZodFunction<z.ZodTuple<readonly [z.ZodString, z.ZodXor<readonly [z.ZodString, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>]>, z.ZodOptional<z.ZodString>], null>, z.ZodPromise<z.ZodVoid>>, z.ZodLiteral<false>]>>>;
|
|
24
|
+
mkdir: z.ZodOptional<z.ZodOptional<z.ZodXor<readonly [z.ZodFunction<z.ZodTuple<readonly [z.ZodString, z.ZodOptional<z.ZodObject<{
|
|
25
|
+
recursive: z.ZodOptional<z.ZodBoolean>;
|
|
26
|
+
}, z.core.$strip>>], null>, z.ZodPromise<z.ZodVoid>>, z.ZodLiteral<false>]>>>;
|
|
27
|
+
rm: z.ZodOptional<z.ZodOptional<z.ZodXor<readonly [z.ZodFunction<z.ZodTuple<readonly [z.ZodString, z.ZodOptional<z.ZodObject<{
|
|
28
|
+
recursive: z.ZodOptional<z.ZodBoolean>;
|
|
29
|
+
force: z.ZodOptional<z.ZodBoolean>;
|
|
30
|
+
}, z.core.$strip>>], null>, z.ZodPromise<z.ZodVoid>>, z.ZodLiteral<false>]>>>;
|
|
31
|
+
}, z.core.$strip>>;
|
|
32
|
+
}, z.core.$strip>>>;
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/fs-bridges/read-only-bridge.d.ts
|
|
35
|
+
interface CreateReadOnlyBridgeOptions {
|
|
36
|
+
/**
|
|
37
|
+
* Mock function for reading files
|
|
38
|
+
* @default vi.fn().mockResolvedValue("content")
|
|
39
|
+
*/
|
|
40
|
+
read?: (path: string) => Promise<string>;
|
|
41
|
+
/**
|
|
42
|
+
* Mock function for checking file existence
|
|
43
|
+
* @default vi.fn().mockResolvedValue(true)
|
|
44
|
+
*/
|
|
45
|
+
exists?: (path: string) => Promise<boolean>;
|
|
46
|
+
/**
|
|
47
|
+
* Mock function for listing directory contents
|
|
48
|
+
* @default vi.fn().mockResolvedValue([])
|
|
49
|
+
*/
|
|
50
|
+
listdir?: (path: string, recursive?: boolean) => Promise<FSEntry[]>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Creates a read-only filesystem bridge for testing.
|
|
54
|
+
*
|
|
55
|
+
* Useful for testing operations that should skip when write capability is unavailable.
|
|
56
|
+
* All functions are optional and will use sensible defaults if not provided.
|
|
57
|
+
*
|
|
58
|
+
* @param options - Optional mock functions for read, exists, and listdir
|
|
59
|
+
* @returns A read-only FileSystemBridge instance
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* import { createReadOnlyBridge } from "#test-utils/fs-bridges";
|
|
64
|
+
* import { vi } from "vitest";
|
|
65
|
+
*
|
|
66
|
+
* // Use defaults
|
|
67
|
+
* const bridge = createReadOnlyBridge();
|
|
68
|
+
*
|
|
69
|
+
* // Custom read function
|
|
70
|
+
* const bridge = createReadOnlyBridge({
|
|
71
|
+
* read: vi.fn().mockResolvedValue("custom content"),
|
|
72
|
+
* });
|
|
73
|
+
*
|
|
74
|
+
* // All custom functions
|
|
75
|
+
* const bridge = createReadOnlyBridge({
|
|
76
|
+
* read: vi.fn().mockResolvedValue("file content"),
|
|
77
|
+
* exists: vi.fn().mockResolvedValue(false),
|
|
78
|
+
* listdir: vi.fn().mockResolvedValue([...]),
|
|
79
|
+
* });
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
declare function createReadOnlyBridge(options?: CreateReadOnlyBridgeOptions): FileSystemBridge;
|
|
83
|
+
//#endregion
|
|
84
|
+
export { type CreateReadOnlyBridgeOptions, createMemoryMockFS, createReadOnlyBridge };
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { vi } from "vitest";
|
|
2
|
+
import { Buffer } from "node:buffer";
|
|
3
|
+
import { appendTrailingSlash, prependLeadingSlash } from "@luxass/utils/path";
|
|
4
|
+
import { defineFileSystemBridge } from "@ucdjs/fs-bridge";
|
|
5
|
+
import { FileEntrySchema } from "@ucdjs/schemas";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
|
|
8
|
+
//#region src/fs-bridges/memory-fs-bridge.ts
|
|
9
|
+
/**
|
|
10
|
+
* Normalizes root path inputs to an empty string.
|
|
11
|
+
* Treats "", ".", "/" and undefined as the empty root.
|
|
12
|
+
*/
|
|
13
|
+
function normalizeRootPath(path) {
|
|
14
|
+
return !path || path === "." || path === "/" ? "" : path;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Formats a relative path to match FSEntry schema requirements (parity with node/http bridges):
|
|
18
|
+
* - Leading slash required for all paths
|
|
19
|
+
* - Trailing slash required for directories
|
|
20
|
+
*/
|
|
21
|
+
function formatEntryPath(relativePath, isDirectory) {
|
|
22
|
+
const withLeadingSlash = prependLeadingSlash(relativePath);
|
|
23
|
+
return isDirectory ? appendTrailingSlash(withLeadingSlash) : withLeadingSlash;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Marker value for explicit directories in the flat Map storage.
|
|
27
|
+
* Directories are stored as "path/" -> DIR_MARKER
|
|
28
|
+
*/
|
|
29
|
+
const DIR_MARKER = Symbol("directory");
|
|
30
|
+
/**
|
|
31
|
+
* Checks if a path represents an explicit directory marker.
|
|
32
|
+
*/
|
|
33
|
+
function isDirMarkerKey(path) {
|
|
34
|
+
return path.endsWith("/");
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Gets the directory marker key for a given path.
|
|
38
|
+
*/
|
|
39
|
+
function getDirMarkerKey(path) {
|
|
40
|
+
const normalized = normalizeRootPath(path);
|
|
41
|
+
if (normalized === "") return "";
|
|
42
|
+
return normalized.endsWith("/") ? normalized : `${normalized}/`;
|
|
43
|
+
}
|
|
44
|
+
const createMemoryMockFS = defineFileSystemBridge({
|
|
45
|
+
meta: {
|
|
46
|
+
name: "In-Memory File System Bridge",
|
|
47
|
+
description: "A simple in-memory file system bridge using a flat Map for storage, perfect for testing."
|
|
48
|
+
},
|
|
49
|
+
optionsSchema: z.object({
|
|
50
|
+
basePath: z.string().optional(),
|
|
51
|
+
initialFiles: z.record(z.string(), z.string()).optional(),
|
|
52
|
+
functions: z.object({
|
|
53
|
+
read: z.xor([z.function({
|
|
54
|
+
input: [z.string()],
|
|
55
|
+
output: z.promise(z.string())
|
|
56
|
+
}), z.literal(false)]).optional(),
|
|
57
|
+
exists: z.xor([z.function({
|
|
58
|
+
input: [z.string()],
|
|
59
|
+
output: z.promise(z.boolean())
|
|
60
|
+
}), z.literal(false)]).optional(),
|
|
61
|
+
listdir: z.xor([z.function({
|
|
62
|
+
input: [z.string(), z.boolean().optional()],
|
|
63
|
+
output: z.promise(z.array(FileEntrySchema))
|
|
64
|
+
}), z.literal(false)]).optional(),
|
|
65
|
+
write: z.xor([z.function({
|
|
66
|
+
input: [
|
|
67
|
+
z.string(),
|
|
68
|
+
z.xor([z.string(), z.instanceof(Uint8Array)]),
|
|
69
|
+
z.string().optional()
|
|
70
|
+
],
|
|
71
|
+
output: z.promise(z.void())
|
|
72
|
+
}), z.literal(false)]).optional(),
|
|
73
|
+
mkdir: z.xor([z.function({
|
|
74
|
+
input: [z.string(), z.object({ recursive: z.boolean().optional() }).optional()],
|
|
75
|
+
output: z.promise(z.void())
|
|
76
|
+
}), z.literal(false)]).optional(),
|
|
77
|
+
rm: z.xor([z.function({
|
|
78
|
+
input: [z.string(), z.object({
|
|
79
|
+
recursive: z.boolean().optional(),
|
|
80
|
+
force: z.boolean().optional()
|
|
81
|
+
}).optional()],
|
|
82
|
+
output: z.promise(z.void())
|
|
83
|
+
}), z.literal(false)]).optional()
|
|
84
|
+
}).partial().optional()
|
|
85
|
+
}).optional(),
|
|
86
|
+
state: { files: /* @__PURE__ */ new Map() },
|
|
87
|
+
setup({ options, state, resolveSafePath }) {
|
|
88
|
+
const basePath = options?.basePath ?? "/";
|
|
89
|
+
const resolve = (path) => {
|
|
90
|
+
return resolveSafePath(basePath, path);
|
|
91
|
+
};
|
|
92
|
+
if (options?.initialFiles) for (const [path, content] of Object.entries(options.initialFiles)) state.files.set(resolve(path), content);
|
|
93
|
+
const operations = {
|
|
94
|
+
read: async (path) => {
|
|
95
|
+
const resolvedPath = resolve(path);
|
|
96
|
+
const content = state.files.get(resolvedPath);
|
|
97
|
+
if (content === void 0) throw new Error(`ENOENT: no such file or directory, open '${resolvedPath}'`);
|
|
98
|
+
if (content === DIR_MARKER) throw new Error(`EISDIR: illegal operation on a directory, read '${resolvedPath}'`);
|
|
99
|
+
return content;
|
|
100
|
+
},
|
|
101
|
+
exists: async (path) => {
|
|
102
|
+
const resolvedPath = resolve(path);
|
|
103
|
+
if (state.files.has(resolvedPath)) return true;
|
|
104
|
+
const dirMarkerKey = getDirMarkerKey(resolvedPath);
|
|
105
|
+
if (dirMarkerKey && state.files.has(dirMarkerKey)) return true;
|
|
106
|
+
const normalizedPath = normalizeRootPath(resolvedPath);
|
|
107
|
+
const pathWithSlash = normalizedPath === "" ? "" : normalizedPath.endsWith("/") ? normalizedPath : `${normalizedPath}/`;
|
|
108
|
+
for (const filePath of state.files.keys()) if (filePath.startsWith(pathWithSlash)) return true;
|
|
109
|
+
return false;
|
|
110
|
+
},
|
|
111
|
+
listdir: async (path, recursive = false) => {
|
|
112
|
+
const resolvedPath = resolve(path);
|
|
113
|
+
const entries = [];
|
|
114
|
+
const normalizedPath = normalizeRootPath(resolvedPath);
|
|
115
|
+
const pathPrefix = normalizedPath === "" ? "" : normalizedPath.endsWith("/") ? normalizedPath : `${normalizedPath}/`;
|
|
116
|
+
const requestedPath = normalizeRootPath(path).replace(/^\/+/, "").replace(/\/+$/, "");
|
|
117
|
+
const prefixToRoot = (relative) => {
|
|
118
|
+
if (!requestedPath) return relative;
|
|
119
|
+
if (!relative) return requestedPath;
|
|
120
|
+
return `${requestedPath}/${relative}`;
|
|
121
|
+
};
|
|
122
|
+
const seenDirs = /* @__PURE__ */ new Set();
|
|
123
|
+
for (const [filePath, value] of state.files.entries()) {
|
|
124
|
+
if (!filePath.startsWith(pathPrefix)) continue;
|
|
125
|
+
const relativePath = filePath.slice(pathPrefix.length);
|
|
126
|
+
if (isDirMarkerKey(filePath) && value === DIR_MARKER) {
|
|
127
|
+
const parts = relativePath.slice(0, -1).split("/");
|
|
128
|
+
if (!recursive) {
|
|
129
|
+
if (parts.length === 1 && parts[0]) {
|
|
130
|
+
const dirName = parts[0];
|
|
131
|
+
if (!seenDirs.has(dirName)) {
|
|
132
|
+
seenDirs.add(dirName);
|
|
133
|
+
entries.push({
|
|
134
|
+
type: "directory",
|
|
135
|
+
name: dirName,
|
|
136
|
+
path: formatEntryPath(prefixToRoot(dirName), true),
|
|
137
|
+
children: []
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
let currentLevel = entries;
|
|
143
|
+
for (let i = 0; i < parts.length; i++) {
|
|
144
|
+
const part = parts[i];
|
|
145
|
+
if (!part) continue;
|
|
146
|
+
const partPath = parts.slice(0, i + 1).join("/");
|
|
147
|
+
let dirEntry = currentLevel.find((e) => e.type === "directory" && e.name === part);
|
|
148
|
+
if (!dirEntry) {
|
|
149
|
+
dirEntry = {
|
|
150
|
+
type: "directory",
|
|
151
|
+
name: part,
|
|
152
|
+
path: formatEntryPath(prefixToRoot(partPath), true),
|
|
153
|
+
children: []
|
|
154
|
+
};
|
|
155
|
+
currentLevel.push(dirEntry);
|
|
156
|
+
}
|
|
157
|
+
currentLevel = dirEntry.children;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const parts = relativePath.split("/");
|
|
163
|
+
if (!recursive) {
|
|
164
|
+
if (parts.length === 1 && parts[0]) entries.push({
|
|
165
|
+
type: "file",
|
|
166
|
+
name: parts[0],
|
|
167
|
+
path: formatEntryPath(prefixToRoot(relativePath), false)
|
|
168
|
+
});
|
|
169
|
+
else if (parts.length > 1 && parts[0]) {
|
|
170
|
+
const dirName = parts[0];
|
|
171
|
+
if (!seenDirs.has(dirName)) {
|
|
172
|
+
seenDirs.add(dirName);
|
|
173
|
+
entries.push({
|
|
174
|
+
type: "directory",
|
|
175
|
+
name: dirName,
|
|
176
|
+
path: formatEntryPath(prefixToRoot(dirName), true),
|
|
177
|
+
children: []
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
let currentLevel = entries;
|
|
183
|
+
for (let i = 0; i < parts.length; i++) {
|
|
184
|
+
const part = parts[i];
|
|
185
|
+
if (!part) continue;
|
|
186
|
+
const isLastPart = i === parts.length - 1;
|
|
187
|
+
const partPath = parts.slice(0, i + 1).join("/");
|
|
188
|
+
if (isLastPart) currentLevel.push({
|
|
189
|
+
type: "file",
|
|
190
|
+
name: part,
|
|
191
|
+
path: formatEntryPath(prefixToRoot(partPath), false)
|
|
192
|
+
});
|
|
193
|
+
else {
|
|
194
|
+
let dirEntry = currentLevel.find((e) => e.type === "directory" && e.name === part);
|
|
195
|
+
if (!dirEntry) {
|
|
196
|
+
dirEntry = {
|
|
197
|
+
type: "directory",
|
|
198
|
+
name: part,
|
|
199
|
+
path: formatEntryPath(prefixToRoot(partPath), true),
|
|
200
|
+
children: []
|
|
201
|
+
};
|
|
202
|
+
currentLevel.push(dirEntry);
|
|
203
|
+
}
|
|
204
|
+
currentLevel = dirEntry.children;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return entries;
|
|
210
|
+
},
|
|
211
|
+
write: async (path, data, encoding = "utf8") => {
|
|
212
|
+
const resolvedPath = resolve(path);
|
|
213
|
+
const content = typeof data === "string" ? data : Buffer.from(data).toString(encoding);
|
|
214
|
+
state.files.set(resolvedPath, content);
|
|
215
|
+
},
|
|
216
|
+
mkdir: async (path) => {
|
|
217
|
+
const normalizedPath = normalizeRootPath(resolve(path));
|
|
218
|
+
if (normalizedPath === "") return;
|
|
219
|
+
const dirMarkerKey = getDirMarkerKey(normalizedPath);
|
|
220
|
+
state.files.set(dirMarkerKey, DIR_MARKER);
|
|
221
|
+
const parts = normalizedPath.split("/");
|
|
222
|
+
for (let i = 1; i < parts.length; i++) {
|
|
223
|
+
const parentMarkerKey = getDirMarkerKey(parts.slice(0, i).join("/"));
|
|
224
|
+
if (parentMarkerKey && !state.files.has(parentMarkerKey)) state.files.set(parentMarkerKey, DIR_MARKER);
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
rm: async (path, options) => {
|
|
228
|
+
const resolvedPath = resolve(path);
|
|
229
|
+
if (state.files.has(resolvedPath)) {
|
|
230
|
+
state.files.delete(resolvedPath);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const dirMarkerKey = getDirMarkerKey(resolvedPath);
|
|
234
|
+
if (dirMarkerKey && state.files.has(dirMarkerKey)) state.files.delete(dirMarkerKey);
|
|
235
|
+
if (options?.recursive) {
|
|
236
|
+
const normalizedPath = normalizeRootPath(resolvedPath);
|
|
237
|
+
const pathPrefix = normalizedPath === "" ? "" : normalizedPath.endsWith("/") ? normalizedPath : `${normalizedPath}/`;
|
|
238
|
+
const keysToDelete = [];
|
|
239
|
+
for (const filePath of state.files.keys()) if (filePath.startsWith(pathPrefix)) keysToDelete.push(filePath);
|
|
240
|
+
for (const key of keysToDelete) state.files.delete(key);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
if (options?.functions) {
|
|
245
|
+
const fns = options.functions;
|
|
246
|
+
for (const key of Object.keys(fns)) {
|
|
247
|
+
const val = fns[key];
|
|
248
|
+
if (val === false) delete operations[key];
|
|
249
|
+
else operations[key] = val;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return operations;
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
//#endregion
|
|
257
|
+
//#region src/fs-bridges/read-only-bridge.ts
|
|
258
|
+
/**
|
|
259
|
+
* Creates a read-only filesystem bridge for testing.
|
|
260
|
+
*
|
|
261
|
+
* Useful for testing operations that should skip when write capability is unavailable.
|
|
262
|
+
* All functions are optional and will use sensible defaults if not provided.
|
|
263
|
+
*
|
|
264
|
+
* @param options - Optional mock functions for read, exists, and listdir
|
|
265
|
+
* @returns A read-only FileSystemBridge instance
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* ```typescript
|
|
269
|
+
* import { createReadOnlyBridge } from "#test-utils/fs-bridges";
|
|
270
|
+
* import { vi } from "vitest";
|
|
271
|
+
*
|
|
272
|
+
* // Use defaults
|
|
273
|
+
* const bridge = createReadOnlyBridge();
|
|
274
|
+
*
|
|
275
|
+
* // Custom read function
|
|
276
|
+
* const bridge = createReadOnlyBridge({
|
|
277
|
+
* read: vi.fn().mockResolvedValue("custom content"),
|
|
278
|
+
* });
|
|
279
|
+
*
|
|
280
|
+
* // All custom functions
|
|
281
|
+
* const bridge = createReadOnlyBridge({
|
|
282
|
+
* read: vi.fn().mockResolvedValue("file content"),
|
|
283
|
+
* exists: vi.fn().mockResolvedValue(false),
|
|
284
|
+
* listdir: vi.fn().mockResolvedValue([...]),
|
|
285
|
+
* });
|
|
286
|
+
* ```
|
|
287
|
+
*/
|
|
288
|
+
function createReadOnlyBridge(options = {}) {
|
|
289
|
+
return defineFileSystemBridge({
|
|
290
|
+
meta: {
|
|
291
|
+
name: "Read-Only Test Bridge",
|
|
292
|
+
description: "A read-only bridge for testing"
|
|
293
|
+
},
|
|
294
|
+
setup: () => ({
|
|
295
|
+
read: options.read ?? vi.fn().mockResolvedValue("content"),
|
|
296
|
+
exists: options.exists ?? vi.fn().mockResolvedValue(true),
|
|
297
|
+
listdir: options.listdir ?? vi.fn().mockResolvedValue([])
|
|
298
|
+
})
|
|
299
|
+
})();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
//#endregion
|
|
303
|
+
export { createMemoryMockFS, createReadOnlyBridge };
|