@symbiosis-lab/moss-api 0.2.0 → 0.3.0

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.
@@ -0,0 +1,253 @@
1
+ //#region src/testing/mock-tauri.d.ts
2
+ /**
3
+ * Tauri IPC mocking utilities for testing Moss plugins
4
+ *
5
+ * Provides in-memory implementations of Tauri IPC commands that plugins use
6
+ * through moss-api. This enables integration testing without a running Tauri app.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { setupMockTauri } from "@symbiosis-lab/moss-api/testing";
11
+ *
12
+ * describe("my plugin", () => {
13
+ * let ctx: MockTauriContext;
14
+ *
15
+ * beforeEach(() => {
16
+ * ctx = setupMockTauri();
17
+ * });
18
+ *
19
+ * afterEach(() => {
20
+ * ctx.cleanup();
21
+ * });
22
+ *
23
+ * it("reads files", async () => {
24
+ * ctx.filesystem.setFile("/project/test.md", "# Hello");
25
+ * const content = await readFile("/project", "test.md");
26
+ * expect(content).toBe("# Hello");
27
+ * });
28
+ * });
29
+ * ```
30
+ */
31
+ /**
32
+ * A file stored in the mock filesystem
33
+ */
34
+ interface MockFile {
35
+ content: string;
36
+ createdAt: Date;
37
+ modifiedAt: Date;
38
+ }
39
+ /**
40
+ * In-memory filesystem for testing file operations
41
+ */
42
+ interface MockFilesystem {
43
+ /** Internal file storage */
44
+ files: Map<string, MockFile>;
45
+ /** Get a file by full path */
46
+ getFile(path: string): MockFile | undefined;
47
+ /** Set a file's content (creates or updates) */
48
+ setFile(path: string, content: string): void;
49
+ /** Delete a file */
50
+ deleteFile(path: string): boolean;
51
+ /** List files matching an optional pattern */
52
+ listFiles(pattern?: string): string[];
53
+ /** Clear all files */
54
+ clear(): void;
55
+ }
56
+ /**
57
+ * Create a new mock filesystem instance
58
+ */
59
+ declare function createMockFilesystem(): MockFilesystem;
60
+ /**
61
+ * Tracks download activity for testing concurrency and completion
62
+ */
63
+ interface DownloadTracker {
64
+ /** Number of currently active downloads */
65
+ activeDownloads: number;
66
+ /** Maximum concurrent downloads observed */
67
+ maxConcurrent: number;
68
+ /** URLs of completed downloads */
69
+ completedDownloads: string[];
70
+ /** Failed downloads with error messages */
71
+ failedDownloads: Array<{
72
+ url: string;
73
+ error: string;
74
+ }>;
75
+ /** Mark a download as started */
76
+ startDownload(url: string): void;
77
+ /** Mark a download as ended */
78
+ endDownload(url: string, success: boolean, error?: string): void;
79
+ /** Reset all tracking state */
80
+ reset(): void;
81
+ }
82
+ /**
83
+ * Create a new download tracker instance
84
+ */
85
+ declare function createDownloadTracker(): DownloadTracker;
86
+ /**
87
+ * Configuration for a mocked URL response
88
+ */
89
+ interface MockUrlResponse {
90
+ /** HTTP status code */
91
+ status: number;
92
+ /** Whether the request was successful (2xx) */
93
+ ok: boolean;
94
+ /** Content-Type header */
95
+ contentType?: string;
96
+ /** Response body as base64 (for fetch_url) */
97
+ bodyBase64?: string;
98
+ /** Number of bytes written (for download_asset) */
99
+ bytesWritten?: number;
100
+ /** Actual file path where asset was saved */
101
+ actualPath?: string;
102
+ /** Artificial delay in milliseconds */
103
+ delay?: number;
104
+ }
105
+ /**
106
+ * URL response configuration for mocking HTTP requests
107
+ */
108
+ interface MockUrlConfig {
109
+ /** Map of URL to response(s) */
110
+ responses: Map<string, MockUrlResponse | MockUrlResponse[]>;
111
+ /** Default response for unregistered URLs */
112
+ defaultResponse: MockUrlResponse;
113
+ /** Set response for a URL (can be single or array for retry testing) */
114
+ setResponse(url: string, response: MockUrlResponse | MockUrlResponse[]): void;
115
+ /** Get response for a URL (handles retry sequences) */
116
+ getResponse(url: string): MockUrlResponse;
117
+ /** Reset all URL configurations */
118
+ reset(): void;
119
+ }
120
+ /**
121
+ * Create a new URL config instance
122
+ */
123
+ declare function createMockUrlConfig(): MockUrlConfig;
124
+ /**
125
+ * Result for a mocked binary execution
126
+ */
127
+ interface MockBinaryResult {
128
+ success: boolean;
129
+ exitCode: number;
130
+ stdout: string;
131
+ stderr: string;
132
+ }
133
+ /**
134
+ * Configuration for mocking binary execution
135
+ */
136
+ interface MockBinaryConfig {
137
+ /** Map of binary commands to results */
138
+ results: Map<string, MockBinaryResult>;
139
+ /** Default result for unregistered binaries */
140
+ defaultResult: MockBinaryResult;
141
+ /** Set result for a binary command (key format: "binaryPath args...") */
142
+ setResult(key: string, result: MockBinaryResult): void;
143
+ /** Get result for a binary command */
144
+ getResult(binaryPath: string, args: string[]): MockBinaryResult;
145
+ /** Reset all configurations */
146
+ reset(): void;
147
+ }
148
+ /**
149
+ * Create a new binary config instance
150
+ */
151
+ declare function createMockBinaryConfig(): MockBinaryConfig;
152
+ /**
153
+ * Mock cookie storage for plugin authentication testing
154
+ */
155
+ interface MockCookieStorage {
156
+ /** Map of pluginName:projectPath to cookies */
157
+ cookies: Map<string, Array<{
158
+ name: string;
159
+ value: string;
160
+ domain?: string;
161
+ path?: string;
162
+ }>>;
163
+ /** Get cookies for a plugin/project */
164
+ getCookies(pluginName: string, projectPath: string): Array<{
165
+ name: string;
166
+ value: string;
167
+ domain?: string;
168
+ path?: string;
169
+ }>;
170
+ /** Set cookies for a plugin/project */
171
+ setCookies(pluginName: string, projectPath: string, cookies: Array<{
172
+ name: string;
173
+ value: string;
174
+ domain?: string;
175
+ path?: string;
176
+ }>): void;
177
+ /** Clear all cookies */
178
+ clear(): void;
179
+ }
180
+ /**
181
+ * Create a new cookie storage instance
182
+ */
183
+ declare function createMockCookieStorage(): MockCookieStorage;
184
+ /**
185
+ * Tracks browser open/close calls for testing
186
+ */
187
+ interface MockBrowserTracker {
188
+ /** URLs that were opened */
189
+ openedUrls: string[];
190
+ /** Number of times closeBrowser was called */
191
+ closeCount: number;
192
+ /** Whether browser is currently open */
193
+ isOpen: boolean;
194
+ /** Reset tracking state */
195
+ reset(): void;
196
+ }
197
+ /**
198
+ * Create a new browser tracker instance
199
+ */
200
+ declare function createMockBrowserTracker(): MockBrowserTracker;
201
+ /**
202
+ * Context returned by setupMockTauri with all mock utilities
203
+ */
204
+ interface MockTauriContext {
205
+ /** In-memory filesystem */
206
+ filesystem: MockFilesystem;
207
+ /** Download tracking for concurrency tests */
208
+ downloadTracker: DownloadTracker;
209
+ /** URL response configuration */
210
+ urlConfig: MockUrlConfig;
211
+ /** Binary execution configuration */
212
+ binaryConfig: MockBinaryConfig;
213
+ /** Cookie storage */
214
+ cookieStorage: MockCookieStorage;
215
+ /** Browser open/close tracking */
216
+ browserTracker: MockBrowserTracker;
217
+ /** Cleanup function - must be called after tests */
218
+ cleanup: () => void;
219
+ }
220
+ /**
221
+ * Set up mock Tauri IPC for testing
222
+ *
223
+ * This sets up `window.__TAURI__.core.invoke` to intercept all IPC calls
224
+ * and route them to in-memory implementations.
225
+ *
226
+ * @returns Context with mock utilities and cleanup function
227
+ *
228
+ * @example
229
+ * ```typescript
230
+ * const ctx = setupMockTauri();
231
+ *
232
+ * // Set up test data
233
+ * ctx.filesystem.setFile("/project/article.md", "# Test");
234
+ * ctx.urlConfig.setResponse("https://example.com/image.png", {
235
+ * status: 200,
236
+ * ok: true,
237
+ * contentType: "image/png",
238
+ * bytesWritten: 1024,
239
+ * });
240
+ *
241
+ * // Run your plugin code...
242
+ *
243
+ * // Verify results
244
+ * expect(ctx.downloadTracker.completedDownloads).toHaveLength(1);
245
+ *
246
+ * // Cleanup
247
+ * ctx.cleanup();
248
+ * ```
249
+ */
250
+ declare function setupMockTauri(): MockTauriContext;
251
+ //#endregion
252
+ export { type DownloadTracker, type MockBinaryConfig, type MockBinaryResult, type MockBrowserTracker, type MockCookieStorage, type MockFile, type MockFilesystem, type MockTauriContext, type MockUrlConfig, type MockUrlResponse, createDownloadTracker, createMockBinaryConfig, createMockBrowserTracker, createMockCookieStorage, createMockFilesystem, createMockUrlConfig, setupMockTauri };
253
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/testing/mock-tauri.ts"],"sourcesContent":[],"mappings":";;AA0CA;AASA;;;;;AAkBA;AA0CA;AAoBA;AAiDA;AAoBA;;;;;;;;;AAgBA;AAiDA;AAUA;;;;;;;AAgBA;AAyCA;;AAEW,UApSM,QAAA,CAoSN;EAKN,OAAA,EAAA,MAAA;EAKQ,SAAA,EA5SA,IA4SA;EAAK,UAAA,EA3SJ,IA2SI;AASlB;AAiCA;AAcA;AA8DA;AAEc,UA7ZG,cAAA,CA6ZH;EAEK;EAEN,KAAA,EA/ZJ,GA+ZI,CAAA,MAAA,EA/ZQ,QA+ZR,CAAA;EAEG;EAEC,OAAA,CAAA,IAAA,EAAA,MAAA,CAAA,EAjaQ,QAiaR,GAAA,SAAA;EAEC;EAAkB,OAAA,CAAA,IAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,CAAA,EAAA,IAAA;EAmCpB;;;;;;;;;;iBAxbA,oBAAA,CAAA,GAAwB;;;;UA0CvB,eAAA;;;;;;;;mBAQE;;;;;;;;;;;;;;iBAYH,qBAAA,CAAA,GAAyB;;;;UAiDxB,eAAA;;;;;;;;;;;;;;;;;;;UAoBA,aAAA;;aAEJ,YAAY,kBAAkB;;mBAExB;;qCAEkB,kBAAkB;;4BAE3B;;;;;;;iBAQZ,mBAAA,CAAA,GAAuB;;;;UAiDtB,gBAAA;;;;;;;;;UAUA,gBAAA;;WAEN,YAAY;;iBAEN;;iCAEgB;;iDAEgB;;;;;;;iBAQjC,sBAAA,CAAA,GAA0B;;;;UAyCzB,iBAAA;;WAEN,YAAY;;;;;;;uDAKlB;;;;;;;+DAKQ;;;;;;;;;;;;iBASG,uBAAA,CAAA,GAA2B;;;;UAiC1B,kBAAA;;;;;;;;;;;;;iBAcD,wBAAA,CAAA,GAA4B;;;;UA8D3B,gBAAA;;cAEH;;mBAEK;;aAEN;;gBAEG;;iBAEC;;kBAEC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAmCF,cAAA,CAAA,GAAkB"}
@@ -0,0 +1,359 @@
1
+ //#region src/testing/mock-tauri.ts
2
+ /**
3
+ * Create a new mock filesystem instance
4
+ */
5
+ function createMockFilesystem() {
6
+ const files = /* @__PURE__ */ new Map();
7
+ return {
8
+ files,
9
+ getFile(path) {
10
+ return files.get(path);
11
+ },
12
+ setFile(path, content) {
13
+ const now = /* @__PURE__ */ new Date();
14
+ const existing = files.get(path);
15
+ files.set(path, {
16
+ content,
17
+ createdAt: existing?.createdAt ?? now,
18
+ modifiedAt: now
19
+ });
20
+ },
21
+ deleteFile(path) {
22
+ return files.delete(path);
23
+ },
24
+ listFiles(pattern) {
25
+ const allPaths = Array.from(files.keys());
26
+ if (!pattern) return allPaths;
27
+ const regex = /* @__PURE__ */ new RegExp("^" + pattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$");
28
+ return allPaths.filter((p) => regex.test(p));
29
+ },
30
+ clear() {
31
+ files.clear();
32
+ }
33
+ };
34
+ }
35
+ /**
36
+ * Create a new download tracker instance
37
+ */
38
+ function createDownloadTracker() {
39
+ let activeDownloads = 0;
40
+ let maxConcurrent = 0;
41
+ const completedDownloads = [];
42
+ const failedDownloads = [];
43
+ return {
44
+ get activeDownloads() {
45
+ return activeDownloads;
46
+ },
47
+ get maxConcurrent() {
48
+ return maxConcurrent;
49
+ },
50
+ get completedDownloads() {
51
+ return completedDownloads;
52
+ },
53
+ get failedDownloads() {
54
+ return failedDownloads;
55
+ },
56
+ startDownload(url) {
57
+ activeDownloads++;
58
+ if (activeDownloads > maxConcurrent) maxConcurrent = activeDownloads;
59
+ },
60
+ endDownload(url, success, error) {
61
+ activeDownloads--;
62
+ if (success) completedDownloads.push(url);
63
+ else failedDownloads.push({
64
+ url,
65
+ error: error || "Unknown error"
66
+ });
67
+ },
68
+ reset() {
69
+ activeDownloads = 0;
70
+ maxConcurrent = 0;
71
+ completedDownloads.length = 0;
72
+ failedDownloads.length = 0;
73
+ }
74
+ };
75
+ }
76
+ /**
77
+ * Create a new URL config instance
78
+ */
79
+ function createMockUrlConfig() {
80
+ const responses = /* @__PURE__ */ new Map();
81
+ const callCounts = /* @__PURE__ */ new Map();
82
+ const defaultResponse = {
83
+ status: 200,
84
+ ok: true,
85
+ contentType: "image/png",
86
+ bodyBase64: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
87
+ bytesWritten: 68,
88
+ actualPath: "assets/image.png"
89
+ };
90
+ return {
91
+ responses,
92
+ defaultResponse,
93
+ setResponse(url, response) {
94
+ responses.set(url, response);
95
+ callCounts.set(url, 0);
96
+ },
97
+ getResponse(url) {
98
+ const config = responses.get(url);
99
+ if (!config) return defaultResponse;
100
+ if (Array.isArray(config)) {
101
+ const count = callCounts.get(url) || 0;
102
+ callCounts.set(url, count + 1);
103
+ return config[Math.min(count, config.length - 1)];
104
+ }
105
+ return config;
106
+ },
107
+ reset() {
108
+ responses.clear();
109
+ callCounts.clear();
110
+ }
111
+ };
112
+ }
113
+ /**
114
+ * Create a new binary config instance
115
+ */
116
+ function createMockBinaryConfig() {
117
+ const results = /* @__PURE__ */ new Map();
118
+ const defaultResult = {
119
+ success: true,
120
+ exitCode: 0,
121
+ stdout: "",
122
+ stderr: ""
123
+ };
124
+ return {
125
+ results,
126
+ defaultResult,
127
+ setResult(key, result) {
128
+ results.set(key, result);
129
+ },
130
+ getResult(binaryPath, args) {
131
+ const exactKey = `${binaryPath} ${args.join(" ")}`.trim();
132
+ if (results.has(exactKey)) return results.get(exactKey);
133
+ if (results.has(binaryPath)) return results.get(binaryPath);
134
+ return defaultResult;
135
+ },
136
+ reset() {
137
+ results.clear();
138
+ }
139
+ };
140
+ }
141
+ /**
142
+ * Create a new cookie storage instance
143
+ */
144
+ function createMockCookieStorage() {
145
+ const cookies = /* @__PURE__ */ new Map();
146
+ return {
147
+ cookies,
148
+ getCookies(pluginName, projectPath) {
149
+ const key = `${pluginName}:${projectPath}`;
150
+ return cookies.get(key) || [];
151
+ },
152
+ setCookies(pluginName, projectPath, newCookies) {
153
+ const key = `${pluginName}:${projectPath}`;
154
+ cookies.set(key, newCookies);
155
+ },
156
+ clear() {
157
+ cookies.clear();
158
+ }
159
+ };
160
+ }
161
+ /**
162
+ * Create a new browser tracker instance
163
+ */
164
+ function createMockBrowserTracker() {
165
+ const openedUrls = [];
166
+ let closeCount = 0;
167
+ let isOpen = false;
168
+ return {
169
+ get openedUrls() {
170
+ return openedUrls;
171
+ },
172
+ get closeCount() {
173
+ return closeCount;
174
+ },
175
+ get isOpen() {
176
+ return isOpen;
177
+ },
178
+ reset() {
179
+ openedUrls.length = 0;
180
+ closeCount = 0;
181
+ isOpen = false;
182
+ }
183
+ };
184
+ }
185
+ /**
186
+ * Extract filename from URL (mimics Rust backend behavior)
187
+ */
188
+ function extractFilenameFromUrl(url) {
189
+ try {
190
+ const segments = new URL(url).pathname.split("/").filter((s) => s.length > 0);
191
+ for (const segment of segments) if (/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(segment)) return `${segment}.png`;
192
+ const lastSegment = segments[segments.length - 1] || "image";
193
+ return lastSegment.includes(".") ? lastSegment : `${lastSegment}.png`;
194
+ } catch {
195
+ return "image.png";
196
+ }
197
+ }
198
+ /**
199
+ * Set up mock Tauri IPC for testing
200
+ *
201
+ * This sets up `window.__TAURI__.core.invoke` to intercept all IPC calls
202
+ * and route them to in-memory implementations.
203
+ *
204
+ * @returns Context with mock utilities and cleanup function
205
+ *
206
+ * @example
207
+ * ```typescript
208
+ * const ctx = setupMockTauri();
209
+ *
210
+ * // Set up test data
211
+ * ctx.filesystem.setFile("/project/article.md", "# Test");
212
+ * ctx.urlConfig.setResponse("https://example.com/image.png", {
213
+ * status: 200,
214
+ * ok: true,
215
+ * contentType: "image/png",
216
+ * bytesWritten: 1024,
217
+ * });
218
+ *
219
+ * // Run your plugin code...
220
+ *
221
+ * // Verify results
222
+ * expect(ctx.downloadTracker.completedDownloads).toHaveLength(1);
223
+ *
224
+ * // Cleanup
225
+ * ctx.cleanup();
226
+ * ```
227
+ */
228
+ function setupMockTauri() {
229
+ const filesystem = createMockFilesystem();
230
+ const downloadTracker = createDownloadTracker();
231
+ const urlConfig = createMockUrlConfig();
232
+ const binaryConfig = createMockBinaryConfig();
233
+ const cookieStorage = createMockCookieStorage();
234
+ const browserTracker = createMockBrowserTracker();
235
+ const invoke = async (cmd, args) => {
236
+ const payload = args;
237
+ switch (cmd) {
238
+ case "read_project_file": {
239
+ const fullPath = `${payload?.projectPath}/${payload?.relativePath}`;
240
+ const file = filesystem.getFile(fullPath);
241
+ if (file) return file.content;
242
+ throw new Error(`File not found: ${fullPath}`);
243
+ }
244
+ case "write_project_file": {
245
+ const projectPath = payload?.projectPath;
246
+ const relativePath = payload?.relativePath;
247
+ const content = payload?.data;
248
+ const fullPath = `${projectPath}/${relativePath}`;
249
+ filesystem.setFile(fullPath, content);
250
+ return null;
251
+ }
252
+ case "list_project_files": {
253
+ const projectPath = payload?.projectPath;
254
+ return filesystem.listFiles().filter((p) => p.startsWith(projectPath + "/")).map((p) => p.substring(projectPath.length + 1));
255
+ }
256
+ case "fetch_url": {
257
+ const url = payload?.url;
258
+ const response = urlConfig.getResponse(url);
259
+ if (response.delay) return new Promise((resolve) => setTimeout(() => resolve({
260
+ status: response.status,
261
+ ok: response.ok,
262
+ body_base64: response.bodyBase64 || "",
263
+ content_type: response.contentType || null
264
+ }), response.delay));
265
+ return {
266
+ status: response.status,
267
+ ok: response.ok,
268
+ body_base64: response.bodyBase64 || "",
269
+ content_type: response.contentType || null
270
+ };
271
+ }
272
+ case "download_asset": {
273
+ const url = payload?.url;
274
+ const targetDir = payload?.targetDir;
275
+ const response = urlConfig.getResponse(url);
276
+ downloadTracker.startDownload(url);
277
+ if (response.status === 0) {
278
+ downloadTracker.endDownload(url, false, "Network error");
279
+ throw new Error("Network timeout");
280
+ }
281
+ const actualPath = response.actualPath || `${targetDir}/${extractFilenameFromUrl(url)}`;
282
+ const result = {
283
+ status: response.status,
284
+ ok: response.ok,
285
+ content_type: response.contentType || null,
286
+ bytes_written: response.bytesWritten || 0,
287
+ actual_path: actualPath
288
+ };
289
+ if (response.delay) return new Promise((resolve) => setTimeout(() => {
290
+ downloadTracker.endDownload(url, response.ok);
291
+ resolve(result);
292
+ }, response.delay));
293
+ downloadTracker.endDownload(url, response.ok);
294
+ return result;
295
+ }
296
+ case "get_plugin_cookie": {
297
+ const pluginName = payload?.pluginName;
298
+ const projectPath = payload?.projectPath;
299
+ return cookieStorage.getCookies(pluginName, projectPath);
300
+ }
301
+ case "set_plugin_cookie": {
302
+ const pluginName = payload?.pluginName;
303
+ const projectPath = payload?.projectPath;
304
+ const cookies = payload?.cookies;
305
+ cookieStorage.setCookies(pluginName, projectPath, cookies);
306
+ return null;
307
+ }
308
+ case "open_plugin_browser": {
309
+ const url = payload?.url;
310
+ browserTracker.openedUrls.push(url);
311
+ browserTracker.isOpen = true;
312
+ return null;
313
+ }
314
+ case "close_plugin_browser":
315
+ browserTracker.closeCount++;
316
+ browserTracker.isOpen = false;
317
+ return null;
318
+ case "execute_binary": {
319
+ const binaryPath = payload?.binaryPath;
320
+ const binaryArgs = payload?.args;
321
+ const result = binaryConfig.getResult(binaryPath, binaryArgs);
322
+ return {
323
+ success: result.success,
324
+ exit_code: result.exitCode,
325
+ stdout: result.stdout,
326
+ stderr: result.stderr
327
+ };
328
+ }
329
+ case "plugin_message": return null;
330
+ default:
331
+ console.warn(`Unhandled IPC command: ${cmd}`);
332
+ return null;
333
+ }
334
+ };
335
+ if (typeof globalThis.window === "undefined") globalThis.window = {};
336
+ const win = globalThis.window;
337
+ win.__TAURI__ = { core: { invoke } };
338
+ return {
339
+ filesystem,
340
+ downloadTracker,
341
+ urlConfig,
342
+ binaryConfig,
343
+ cookieStorage,
344
+ browserTracker,
345
+ cleanup: () => {
346
+ delete win.__TAURI__;
347
+ filesystem.clear();
348
+ downloadTracker.reset();
349
+ urlConfig.reset();
350
+ binaryConfig.reset();
351
+ cookieStorage.clear();
352
+ browserTracker.reset();
353
+ }
354
+ };
355
+ }
356
+
357
+ //#endregion
358
+ export { createDownloadTracker, createMockBinaryConfig, createMockBrowserTracker, createMockCookieStorage, createMockFilesystem, createMockUrlConfig, setupMockTauri };
359
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["completedDownloads: string[]","failedDownloads: Array<{ url: string; error: string }>","defaultResponse: MockUrlResponse","defaultResult: MockBinaryResult","openedUrls: string[]"],"sources":["../../src/testing/mock-tauri.ts"],"sourcesContent":["/**\n * Tauri IPC mocking utilities for testing Moss plugins\n *\n * Provides in-memory implementations of Tauri IPC commands that plugins use\n * through moss-api. This enables integration testing without a running Tauri app.\n *\n * @example\n * ```typescript\n * import { setupMockTauri } from \"@symbiosis-lab/moss-api/testing\";\n *\n * describe(\"my plugin\", () => {\n * let ctx: MockTauriContext;\n *\n * beforeEach(() => {\n * ctx = setupMockTauri();\n * });\n *\n * afterEach(() => {\n * ctx.cleanup();\n * });\n *\n * it(\"reads files\", async () => {\n * ctx.filesystem.setFile(\"/project/test.md\", \"# Hello\");\n * const content = await readFile(\"/project\", \"test.md\");\n * expect(content).toBe(\"# Hello\");\n * });\n * });\n * ```\n */\n\n// Define minimal types for invoke args\ninterface InvokeArgs {\n [key: string]: unknown;\n}\n\n// ============================================================================\n// Mock Filesystem\n// ============================================================================\n\n/**\n * A file stored in the mock filesystem\n */\nexport interface MockFile {\n content: string;\n createdAt: Date;\n modifiedAt: Date;\n}\n\n/**\n * In-memory filesystem for testing file operations\n */\nexport interface MockFilesystem {\n /** Internal file storage */\n files: Map<string, MockFile>;\n /** Get a file by full path */\n getFile(path: string): MockFile | undefined;\n /** Set a file's content (creates or updates) */\n setFile(path: string, content: string): void;\n /** Delete a file */\n deleteFile(path: string): boolean;\n /** List files matching an optional pattern */\n listFiles(pattern?: string): string[];\n /** Clear all files */\n clear(): void;\n}\n\n/**\n * Create a new mock filesystem instance\n */\nexport function createMockFilesystem(): MockFilesystem {\n const files = new Map<string, MockFile>();\n\n return {\n files,\n getFile(path: string) {\n return files.get(path);\n },\n setFile(path: string, content: string) {\n const now = new Date();\n const existing = files.get(path);\n files.set(path, {\n content,\n createdAt: existing?.createdAt ?? now,\n modifiedAt: now,\n });\n },\n deleteFile(path: string) {\n return files.delete(path);\n },\n listFiles(pattern?: string) {\n const allPaths = Array.from(files.keys());\n if (!pattern) return allPaths;\n // Simple glob matching\n const regex = new RegExp(\n \"^\" + pattern.replace(/\\*/g, \".*\").replace(/\\?/g, \".\") + \"$\"\n );\n return allPaths.filter((p) => regex.test(p));\n },\n clear() {\n files.clear();\n },\n };\n}\n\n// ============================================================================\n// Download Tracker\n// ============================================================================\n\n/**\n * Tracks download activity for testing concurrency and completion\n */\nexport interface DownloadTracker {\n /** Number of currently active downloads */\n activeDownloads: number;\n /** Maximum concurrent downloads observed */\n maxConcurrent: number;\n /** URLs of completed downloads */\n completedDownloads: string[];\n /** Failed downloads with error messages */\n failedDownloads: Array<{ url: string; error: string }>;\n /** Mark a download as started */\n startDownload(url: string): void;\n /** Mark a download as ended */\n endDownload(url: string, success: boolean, error?: string): void;\n /** Reset all tracking state */\n reset(): void;\n}\n\n/**\n * Create a new download tracker instance\n */\nexport function createDownloadTracker(): DownloadTracker {\n let activeDownloads = 0;\n let maxConcurrent = 0;\n const completedDownloads: string[] = [];\n const failedDownloads: Array<{ url: string; error: string }> = [];\n\n return {\n get activeDownloads() {\n return activeDownloads;\n },\n get maxConcurrent() {\n return maxConcurrent;\n },\n get completedDownloads() {\n return completedDownloads;\n },\n get failedDownloads() {\n return failedDownloads;\n },\n startDownload(url: string) {\n activeDownloads++;\n if (activeDownloads > maxConcurrent) {\n maxConcurrent = activeDownloads;\n }\n },\n endDownload(url: string, success: boolean, error?: string) {\n activeDownloads--;\n if (success) {\n completedDownloads.push(url);\n } else {\n failedDownloads.push({ url, error: error || \"Unknown error\" });\n }\n },\n reset() {\n activeDownloads = 0;\n maxConcurrent = 0;\n completedDownloads.length = 0;\n failedDownloads.length = 0;\n },\n };\n}\n\n// ============================================================================\n// URL Response Configuration\n// ============================================================================\n\n/**\n * Configuration for a mocked URL response\n */\nexport interface MockUrlResponse {\n /** HTTP status code */\n status: number;\n /** Whether the request was successful (2xx) */\n ok: boolean;\n /** Content-Type header */\n contentType?: string;\n /** Response body as base64 (for fetch_url) */\n bodyBase64?: string;\n /** Number of bytes written (for download_asset) */\n bytesWritten?: number;\n /** Actual file path where asset was saved */\n actualPath?: string;\n /** Artificial delay in milliseconds */\n delay?: number;\n}\n\n/**\n * URL response configuration for mocking HTTP requests\n */\nexport interface MockUrlConfig {\n /** Map of URL to response(s) */\n responses: Map<string, MockUrlResponse | MockUrlResponse[]>;\n /** Default response for unregistered URLs */\n defaultResponse: MockUrlResponse;\n /** Set response for a URL (can be single or array for retry testing) */\n setResponse(url: string, response: MockUrlResponse | MockUrlResponse[]): void;\n /** Get response for a URL (handles retry sequences) */\n getResponse(url: string): MockUrlResponse;\n /** Reset all URL configurations */\n reset(): void;\n}\n\n/**\n * Create a new URL config instance\n */\nexport function createMockUrlConfig(): MockUrlConfig {\n const responses = new Map<string, MockUrlResponse | MockUrlResponse[]>();\n const callCounts = new Map<string, number>();\n\n // Default: 1x1 red PNG\n const defaultResponse: MockUrlResponse = {\n status: 200,\n ok: true,\n contentType: \"image/png\",\n bodyBase64:\n \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==\",\n bytesWritten: 68,\n actualPath: \"assets/image.png\",\n };\n\n return {\n responses,\n defaultResponse,\n setResponse(url: string, response: MockUrlResponse | MockUrlResponse[]) {\n responses.set(url, response);\n callCounts.set(url, 0);\n },\n getResponse(url: string): MockUrlResponse {\n const config = responses.get(url);\n if (!config) return defaultResponse;\n\n if (Array.isArray(config)) {\n const count = callCounts.get(url) || 0;\n callCounts.set(url, count + 1);\n // Return the response at the current index, or the last one if exceeded\n return config[Math.min(count, config.length - 1)];\n }\n\n return config;\n },\n reset() {\n responses.clear();\n callCounts.clear();\n },\n };\n}\n\n// ============================================================================\n// Binary Execution Tracker\n// ============================================================================\n\n/**\n * Result for a mocked binary execution\n */\nexport interface MockBinaryResult {\n success: boolean;\n exitCode: number;\n stdout: string;\n stderr: string;\n}\n\n/**\n * Configuration for mocking binary execution\n */\nexport interface MockBinaryConfig {\n /** Map of binary commands to results */\n results: Map<string, MockBinaryResult>;\n /** Default result for unregistered binaries */\n defaultResult: MockBinaryResult;\n /** Set result for a binary command (key format: \"binaryPath args...\") */\n setResult(key: string, result: MockBinaryResult): void;\n /** Get result for a binary command */\n getResult(binaryPath: string, args: string[]): MockBinaryResult;\n /** Reset all configurations */\n reset(): void;\n}\n\n/**\n * Create a new binary config instance\n */\nexport function createMockBinaryConfig(): MockBinaryConfig {\n const results = new Map<string, MockBinaryResult>();\n\n const defaultResult: MockBinaryResult = {\n success: true,\n exitCode: 0,\n stdout: \"\",\n stderr: \"\",\n };\n\n return {\n results,\n defaultResult,\n setResult(key: string, result: MockBinaryResult) {\n results.set(key, result);\n },\n getResult(binaryPath: string, args: string[]): MockBinaryResult {\n // Try exact match first\n const exactKey = `${binaryPath} ${args.join(\" \")}`.trim();\n if (results.has(exactKey)) {\n return results.get(exactKey)!;\n }\n // Try binary name only\n if (results.has(binaryPath)) {\n return results.get(binaryPath)!;\n }\n return defaultResult;\n },\n reset() {\n results.clear();\n },\n };\n}\n\n// ============================================================================\n// Cookie Storage\n// ============================================================================\n\n/**\n * Mock cookie storage for plugin authentication testing\n */\nexport interface MockCookieStorage {\n /** Map of pluginName:projectPath to cookies */\n cookies: Map<string, Array<{ name: string; value: string; domain?: string; path?: string }>>;\n /** Get cookies for a plugin/project */\n getCookies(\n pluginName: string,\n projectPath: string\n ): Array<{ name: string; value: string; domain?: string; path?: string }>;\n /** Set cookies for a plugin/project */\n setCookies(\n pluginName: string,\n projectPath: string,\n cookies: Array<{ name: string; value: string; domain?: string; path?: string }>\n ): void;\n /** Clear all cookies */\n clear(): void;\n}\n\n/**\n * Create a new cookie storage instance\n */\nexport function createMockCookieStorage(): MockCookieStorage {\n const cookies = new Map<\n string,\n Array<{ name: string; value: string; domain?: string; path?: string }>\n >();\n\n return {\n cookies,\n getCookies(pluginName: string, projectPath: string) {\n const key = `${pluginName}:${projectPath}`;\n return cookies.get(key) || [];\n },\n setCookies(\n pluginName: string,\n projectPath: string,\n newCookies: Array<{ name: string; value: string; domain?: string; path?: string }>\n ) {\n const key = `${pluginName}:${projectPath}`;\n cookies.set(key, newCookies);\n },\n clear() {\n cookies.clear();\n },\n };\n}\n\n// ============================================================================\n// Browser Tracker\n// ============================================================================\n\n/**\n * Tracks browser open/close calls for testing\n */\nexport interface MockBrowserTracker {\n /** URLs that were opened */\n openedUrls: string[];\n /** Number of times closeBrowser was called */\n closeCount: number;\n /** Whether browser is currently open */\n isOpen: boolean;\n /** Reset tracking state */\n reset(): void;\n}\n\n/**\n * Create a new browser tracker instance\n */\nexport function createMockBrowserTracker(): MockBrowserTracker {\n const openedUrls: string[] = [];\n let closeCount = 0;\n let isOpen = false;\n\n return {\n get openedUrls() {\n return openedUrls;\n },\n get closeCount() {\n return closeCount;\n },\n get isOpen() {\n return isOpen;\n },\n reset() {\n openedUrls.length = 0;\n closeCount = 0;\n isOpen = false;\n },\n };\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Extract filename from URL (mimics Rust backend behavior)\n */\nfunction extractFilenameFromUrl(url: string): string {\n try {\n const urlObj = new URL(url);\n const pathname = urlObj.pathname;\n const segments = pathname.split(\"/\").filter((s) => s.length > 0);\n\n // Try to find UUID in path\n for (const segment of segments) {\n if (\n /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(\n segment\n )\n ) {\n return `${segment}.png`; // Default to PNG for mock\n }\n }\n\n // Fallback to last segment or hash\n const lastSegment = segments[segments.length - 1] || \"image\";\n return lastSegment.includes(\".\") ? lastSegment : `${lastSegment}.png`;\n } catch {\n return \"image.png\";\n }\n}\n\n// ============================================================================\n// Main Setup Function\n// ============================================================================\n\n/**\n * Context returned by setupMockTauri with all mock utilities\n */\nexport interface MockTauriContext {\n /** In-memory filesystem */\n filesystem: MockFilesystem;\n /** Download tracking for concurrency tests */\n downloadTracker: DownloadTracker;\n /** URL response configuration */\n urlConfig: MockUrlConfig;\n /** Binary execution configuration */\n binaryConfig: MockBinaryConfig;\n /** Cookie storage */\n cookieStorage: MockCookieStorage;\n /** Browser open/close tracking */\n browserTracker: MockBrowserTracker;\n /** Cleanup function - must be called after tests */\n cleanup: () => void;\n}\n\n/**\n * Set up mock Tauri IPC for testing\n *\n * This sets up `window.__TAURI__.core.invoke` to intercept all IPC calls\n * and route them to in-memory implementations.\n *\n * @returns Context with mock utilities and cleanup function\n *\n * @example\n * ```typescript\n * const ctx = setupMockTauri();\n *\n * // Set up test data\n * ctx.filesystem.setFile(\"/project/article.md\", \"# Test\");\n * ctx.urlConfig.setResponse(\"https://example.com/image.png\", {\n * status: 200,\n * ok: true,\n * contentType: \"image/png\",\n * bytesWritten: 1024,\n * });\n *\n * // Run your plugin code...\n *\n * // Verify results\n * expect(ctx.downloadTracker.completedDownloads).toHaveLength(1);\n *\n * // Cleanup\n * ctx.cleanup();\n * ```\n */\nexport function setupMockTauri(): MockTauriContext {\n const filesystem = createMockFilesystem();\n const downloadTracker = createDownloadTracker();\n const urlConfig = createMockUrlConfig();\n const binaryConfig = createMockBinaryConfig();\n const cookieStorage = createMockCookieStorage();\n const browserTracker = createMockBrowserTracker();\n\n // Create invoke handler\n const invoke = async (cmd: string, args?: InvokeArgs): Promise<unknown> => {\n const payload = args as Record<string, unknown> | undefined;\n\n switch (cmd) {\n // ======================================================================\n // Filesystem Operations\n // ======================================================================\n case \"read_project_file\": {\n const projectPath = payload?.projectPath as string;\n const relativePath = payload?.relativePath as string;\n const fullPath = `${projectPath}/${relativePath}`;\n const file = filesystem.getFile(fullPath);\n if (file) {\n return file.content;\n }\n throw new Error(`File not found: ${fullPath}`);\n }\n\n case \"write_project_file\": {\n const projectPath = payload?.projectPath as string;\n const relativePath = payload?.relativePath as string;\n const content = payload?.data as string; // Note: moss-api uses 'data' not 'content'\n const fullPath = `${projectPath}/${relativePath}`;\n filesystem.setFile(fullPath, content);\n return null;\n }\n\n case \"list_project_files\": {\n const projectPath = payload?.projectPath as string;\n // Return all file paths relative to the project path\n const allPaths = filesystem.listFiles();\n return allPaths\n .filter((p) => p.startsWith(projectPath + \"/\"))\n .map((p) => p.substring(projectPath.length + 1));\n }\n\n // ======================================================================\n // HTTP Operations\n // ======================================================================\n case \"fetch_url\": {\n const url = payload?.url as string;\n const response = urlConfig.getResponse(url);\n\n if (response.delay) {\n return new Promise((resolve) =>\n setTimeout(\n () =>\n resolve({\n status: response.status,\n ok: response.ok,\n body_base64: response.bodyBase64 || \"\",\n content_type: response.contentType || null,\n }),\n response.delay\n )\n );\n }\n\n return {\n status: response.status,\n ok: response.ok,\n body_base64: response.bodyBase64 || \"\",\n content_type: response.contentType || null,\n };\n }\n\n case \"download_asset\": {\n const url = payload?.url as string;\n const targetDir = payload?.targetDir as string;\n const response = urlConfig.getResponse(url);\n\n downloadTracker.startDownload(url);\n\n // status 0 simulates a network error/timeout - throw an error\n if (response.status === 0) {\n downloadTracker.endDownload(url, false, \"Network error\");\n throw new Error(\"Network timeout\");\n }\n\n // Generate actual_path based on URL or use configured value\n const actualPath =\n response.actualPath || `${targetDir}/${extractFilenameFromUrl(url)}`;\n\n const result = {\n status: response.status,\n ok: response.ok,\n content_type: response.contentType || null,\n bytes_written: response.bytesWritten || 0,\n actual_path: actualPath,\n };\n\n if (response.delay) {\n return new Promise((resolve) =>\n setTimeout(() => {\n downloadTracker.endDownload(url, response.ok);\n resolve(result);\n }, response.delay)\n );\n }\n\n downloadTracker.endDownload(url, response.ok);\n return result;\n }\n\n // ======================================================================\n // Cookie Operations\n // ======================================================================\n case \"get_plugin_cookie\": {\n const pluginName = payload?.pluginName as string;\n const projectPath = payload?.projectPath as string;\n return cookieStorage.getCookies(pluginName, projectPath);\n }\n\n case \"set_plugin_cookie\": {\n const pluginName = payload?.pluginName as string;\n const projectPath = payload?.projectPath as string;\n const cookies = payload?.cookies as Array<{\n name: string;\n value: string;\n domain?: string;\n path?: string;\n }>;\n cookieStorage.setCookies(pluginName, projectPath, cookies);\n return null;\n }\n\n // ======================================================================\n // Browser Operations\n // ======================================================================\n case \"open_plugin_browser\": {\n const url = payload?.url as string;\n browserTracker.openedUrls.push(url);\n (browserTracker as { isOpen: boolean }).isOpen = true;\n return null;\n }\n\n case \"close_plugin_browser\": {\n (browserTracker as { closeCount: number }).closeCount++;\n (browserTracker as { isOpen: boolean }).isOpen = false;\n return null;\n }\n\n // ======================================================================\n // Binary Execution\n // ======================================================================\n case \"execute_binary\": {\n const binaryPath = payload?.binaryPath as string;\n const binaryArgs = payload?.args as string[];\n const result = binaryConfig.getResult(binaryPath, binaryArgs);\n\n return {\n success: result.success,\n exit_code: result.exitCode,\n stdout: result.stdout,\n stderr: result.stderr,\n };\n }\n\n // ======================================================================\n // Messaging (silent no-op)\n // ======================================================================\n case \"plugin_message\": {\n // Silently accept plugin messages (logs, progress, errors, etc.)\n return null;\n }\n\n default:\n console.warn(`Unhandled IPC command: ${cmd}`);\n return null;\n }\n };\n\n // Set up window.__TAURI__ directly (moss-api checks for this)\n const w = globalThis as unknown as {\n window?: {\n __TAURI__?: { core?: { invoke: typeof invoke } };\n };\n };\n\n // Ensure window exists (for Node.js environments like happy-dom)\n if (typeof w.window === \"undefined\") {\n (globalThis as unknown as { window: object }).window = {};\n }\n\n const win = (globalThis as unknown as { window: { __TAURI__?: { core?: { invoke: typeof invoke } } } }).window;\n win.__TAURI__ = {\n core: { invoke },\n };\n\n return {\n filesystem,\n downloadTracker,\n urlConfig,\n binaryConfig,\n cookieStorage,\n browserTracker,\n cleanup: () => {\n // Clear the mock Tauri interface\n delete win.__TAURI__;\n filesystem.clear();\n downloadTracker.reset();\n urlConfig.reset();\n binaryConfig.reset();\n cookieStorage.clear();\n browserTracker.reset();\n },\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbiosis-lab/moss-api",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Official API for building Moss plugins - types and utilities for plugin development",
5
5
  "type": "module",
6
6
  "main": "./dist/index.mjs",
@@ -10,6 +10,10 @@
10
10
  ".": {
11
11
  "import": "./dist/index.mjs",
12
12
  "types": "./dist/index.d.mts"
13
+ },
14
+ "./testing": {
15
+ "import": "./dist/testing/index.mjs",
16
+ "types": "./dist/testing/index.d.mts"
13
17
  }
14
18
  },
15
19
  "files": [