pushwork 1.0.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.
Files changed (184) hide show
  1. package/README.md +460 -0
  2. package/dist/browser/browser-sync-engine.d.ts +64 -0
  3. package/dist/browser/browser-sync-engine.d.ts.map +1 -0
  4. package/dist/browser/browser-sync-engine.js +303 -0
  5. package/dist/browser/browser-sync-engine.js.map +1 -0
  6. package/dist/browser/filesystem-adapter.d.ts +84 -0
  7. package/dist/browser/filesystem-adapter.d.ts.map +1 -0
  8. package/dist/browser/filesystem-adapter.js +413 -0
  9. package/dist/browser/filesystem-adapter.js.map +1 -0
  10. package/dist/browser/index.d.ts +36 -0
  11. package/dist/browser/index.d.ts.map +1 -0
  12. package/dist/browser/index.js +90 -0
  13. package/dist/browser/index.js.map +1 -0
  14. package/dist/browser/types.d.ts +70 -0
  15. package/dist/browser/types.d.ts.map +1 -0
  16. package/dist/browser/types.js +6 -0
  17. package/dist/browser/types.js.map +1 -0
  18. package/dist/cli/commands.d.ts +71 -0
  19. package/dist/cli/commands.d.ts.map +1 -0
  20. package/dist/cli/commands.js +794 -0
  21. package/dist/cli/commands.js.map +1 -0
  22. package/dist/cli/index.d.ts +2 -0
  23. package/dist/cli/index.d.ts.map +1 -0
  24. package/dist/cli/index.js +19 -0
  25. package/dist/cli/index.js.map +1 -0
  26. package/dist/cli.d.ts +3 -0
  27. package/dist/cli.d.ts.map +1 -0
  28. package/dist/cli.js +199 -0
  29. package/dist/cli.js.map +1 -0
  30. package/dist/config/index.d.ts +71 -0
  31. package/dist/config/index.d.ts.map +1 -0
  32. package/dist/config/index.js +314 -0
  33. package/dist/config/index.js.map +1 -0
  34. package/dist/core/change-detection.d.ts +78 -0
  35. package/dist/core/change-detection.d.ts.map +1 -0
  36. package/dist/core/change-detection.js +370 -0
  37. package/dist/core/change-detection.js.map +1 -0
  38. package/dist/core/index.d.ts +5 -0
  39. package/dist/core/index.d.ts.map +1 -0
  40. package/dist/core/index.js +22 -0
  41. package/dist/core/index.js.map +1 -0
  42. package/dist/core/isomorphic-snapshot.d.ts +58 -0
  43. package/dist/core/isomorphic-snapshot.d.ts.map +1 -0
  44. package/dist/core/isomorphic-snapshot.js +204 -0
  45. package/dist/core/isomorphic-snapshot.js.map +1 -0
  46. package/dist/core/move-detection.d.ts +72 -0
  47. package/dist/core/move-detection.d.ts.map +1 -0
  48. package/dist/core/move-detection.js +200 -0
  49. package/dist/core/move-detection.js.map +1 -0
  50. package/dist/core/snapshot.d.ts +109 -0
  51. package/dist/core/snapshot.d.ts.map +1 -0
  52. package/dist/core/snapshot.js +263 -0
  53. package/dist/core/snapshot.js.map +1 -0
  54. package/dist/core/sync-engine.d.ts +110 -0
  55. package/dist/core/sync-engine.d.ts.map +1 -0
  56. package/dist/core/sync-engine.js +817 -0
  57. package/dist/core/sync-engine.js.map +1 -0
  58. package/dist/index.d.ts +6 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +27 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/platform/browser-filesystem.d.ts +26 -0
  63. package/dist/platform/browser-filesystem.d.ts.map +1 -0
  64. package/dist/platform/browser-filesystem.js +91 -0
  65. package/dist/platform/browser-filesystem.js.map +1 -0
  66. package/dist/platform/filesystem.d.ts +29 -0
  67. package/dist/platform/filesystem.d.ts.map +1 -0
  68. package/dist/platform/filesystem.js +65 -0
  69. package/dist/platform/filesystem.js.map +1 -0
  70. package/dist/platform/node-filesystem.d.ts +21 -0
  71. package/dist/platform/node-filesystem.d.ts.map +1 -0
  72. package/dist/platform/node-filesystem.js +93 -0
  73. package/dist/platform/node-filesystem.js.map +1 -0
  74. package/dist/types/config.d.ts +119 -0
  75. package/dist/types/config.d.ts.map +1 -0
  76. package/dist/types/config.js +3 -0
  77. package/dist/types/config.js.map +1 -0
  78. package/dist/types/documents.d.ts +70 -0
  79. package/dist/types/documents.d.ts.map +1 -0
  80. package/dist/types/documents.js +23 -0
  81. package/dist/types/documents.js.map +1 -0
  82. package/dist/types/index.d.ts +4 -0
  83. package/dist/types/index.d.ts.map +1 -0
  84. package/dist/types/index.js +23 -0
  85. package/dist/types/index.js.map +1 -0
  86. package/dist/types/snapshot.d.ts +81 -0
  87. package/dist/types/snapshot.d.ts.map +1 -0
  88. package/dist/types/snapshot.js +17 -0
  89. package/dist/types/snapshot.js.map +1 -0
  90. package/dist/utils/content-similarity.d.ts +53 -0
  91. package/dist/utils/content-similarity.d.ts.map +1 -0
  92. package/dist/utils/content-similarity.js +155 -0
  93. package/dist/utils/content-similarity.js.map +1 -0
  94. package/dist/utils/content.d.ts +5 -0
  95. package/dist/utils/content.d.ts.map +1 -0
  96. package/dist/utils/content.js +30 -0
  97. package/dist/utils/content.js.map +1 -0
  98. package/dist/utils/fs-browser.d.ts +57 -0
  99. package/dist/utils/fs-browser.d.ts.map +1 -0
  100. package/dist/utils/fs-browser.js +311 -0
  101. package/dist/utils/fs-browser.js.map +1 -0
  102. package/dist/utils/fs-node.d.ts +53 -0
  103. package/dist/utils/fs-node.d.ts.map +1 -0
  104. package/dist/utils/fs-node.js +220 -0
  105. package/dist/utils/fs-node.js.map +1 -0
  106. package/dist/utils/fs.d.ts +62 -0
  107. package/dist/utils/fs.d.ts.map +1 -0
  108. package/dist/utils/fs.js +293 -0
  109. package/dist/utils/fs.js.map +1 -0
  110. package/dist/utils/index.d.ts +4 -0
  111. package/dist/utils/index.d.ts.map +1 -0
  112. package/dist/utils/index.js +23 -0
  113. package/dist/utils/index.js.map +1 -0
  114. package/dist/utils/isomorphic.d.ts +29 -0
  115. package/dist/utils/isomorphic.d.ts.map +1 -0
  116. package/dist/utils/isomorphic.js +139 -0
  117. package/dist/utils/isomorphic.js.map +1 -0
  118. package/dist/utils/mime-types.d.ts +13 -0
  119. package/dist/utils/mime-types.d.ts.map +1 -0
  120. package/dist/utils/mime-types.js +240 -0
  121. package/dist/utils/mime-types.js.map +1 -0
  122. package/dist/utils/network-sync.d.ts +12 -0
  123. package/dist/utils/network-sync.d.ts.map +1 -0
  124. package/dist/utils/network-sync.js +149 -0
  125. package/dist/utils/network-sync.js.map +1 -0
  126. package/dist/utils/pure.d.ts +25 -0
  127. package/dist/utils/pure.d.ts.map +1 -0
  128. package/dist/utils/pure.js +112 -0
  129. package/dist/utils/pure.js.map +1 -0
  130. package/dist/utils/repo-factory.d.ts +11 -0
  131. package/dist/utils/repo-factory.d.ts.map +1 -0
  132. package/dist/utils/repo-factory.js +77 -0
  133. package/dist/utils/repo-factory.js.map +1 -0
  134. package/package.json +83 -0
  135. package/src/cli/commands.ts +1053 -0
  136. package/src/cli/index.ts +2 -0
  137. package/src/cli.ts +287 -0
  138. package/src/config/index.ts +334 -0
  139. package/src/core/change-detection.ts +484 -0
  140. package/src/core/index.ts +5 -0
  141. package/src/core/move-detection.ts +269 -0
  142. package/src/core/snapshot.ts +285 -0
  143. package/src/core/sync-engine.ts +1167 -0
  144. package/src/index.ts +14 -0
  145. package/src/types/config.ts +130 -0
  146. package/src/types/documents.ts +72 -0
  147. package/src/types/index.ts +8 -0
  148. package/src/types/snapshot.ts +88 -0
  149. package/src/utils/content-similarity.ts +194 -0
  150. package/src/utils/content.ts +28 -0
  151. package/src/utils/fs.ts +289 -0
  152. package/src/utils/index.ts +8 -0
  153. package/src/utils/mime-types.ts +236 -0
  154. package/src/utils/network-sync.ts +153 -0
  155. package/src/utils/repo-factory.ts +58 -0
  156. package/test/README-TESTING-GAPS.md +174 -0
  157. package/test/integration/README.md +328 -0
  158. package/test/integration/clone-test.sh +310 -0
  159. package/test/integration/conflict-resolution-test.sh +309 -0
  160. package/test/integration/deletion-behavior-test.sh +487 -0
  161. package/test/integration/deletion-sync-test-simple.sh +193 -0
  162. package/test/integration/deletion-sync-test.sh +297 -0
  163. package/test/integration/exclude-patterns.test.ts +152 -0
  164. package/test/integration/full-integration-test.sh +363 -0
  165. package/test/integration/sync-deletion.test.ts +339 -0
  166. package/test/integration/sync-flow.test.ts +309 -0
  167. package/test/run-tests.sh +225 -0
  168. package/test/unit/content-similarity.test.ts +236 -0
  169. package/test/unit/deletion-behavior.test.ts +260 -0
  170. package/test/unit/enhanced-mime-detection.test.ts +266 -0
  171. package/test/unit/snapshot.test.ts +431 -0
  172. package/test/unit/sync-timing.test.ts +178 -0
  173. package/test/unit/utils.test.ts +368 -0
  174. package/tools/browser-sync/README.md +116 -0
  175. package/tools/browser-sync/package.json +44 -0
  176. package/tools/browser-sync/patchwork.json +1 -0
  177. package/tools/browser-sync/pnpm-lock.yaml +4202 -0
  178. package/tools/browser-sync/src/components/BrowserSyncTool.tsx +599 -0
  179. package/tools/browser-sync/src/index.ts +20 -0
  180. package/tools/browser-sync/src/polyfills.ts +31 -0
  181. package/tools/browser-sync/src/styles.css +290 -0
  182. package/tools/browser-sync/src/types.ts +27 -0
  183. package/tools/browser-sync/vite.config.ts +25 -0
  184. package/tsconfig.json +22 -0
@@ -0,0 +1,236 @@
1
+ import { ContentSimilarity } from "../../src/utils/content-similarity";
2
+
3
+ describe("ContentSimilarity", () => {
4
+ describe("calculateSimilarity", () => {
5
+ it("should return 1.0 for identical strings", async () => {
6
+ const content1 = "Hello, world!";
7
+ const content2 = "Hello, world!";
8
+
9
+ const similarity = await ContentSimilarity.calculateSimilarity(
10
+ content1,
11
+ content2
12
+ );
13
+
14
+ expect(similarity).toBe(1.0);
15
+ });
16
+
17
+ it("should return 1.0 for identical binary content", async () => {
18
+ const content1 = new Uint8Array([0x00, 0x01, 0x02, 0x03]);
19
+ const content2 = new Uint8Array([0x00, 0x01, 0x02, 0x03]);
20
+
21
+ const similarity = await ContentSimilarity.calculateSimilarity(
22
+ content1,
23
+ content2
24
+ );
25
+
26
+ expect(similarity).toBe(1.0);
27
+ });
28
+
29
+ it("should return 0.0 for very different content sizes", async () => {
30
+ const content1 = "short";
31
+ const content2 = "a".repeat(1000); // Much longer
32
+
33
+ const similarity = await ContentSimilarity.calculateSimilarity(
34
+ content1,
35
+ content2
36
+ );
37
+
38
+ expect(similarity).toBe(0.0);
39
+ });
40
+
41
+ it("should return high similarity for slightly different content", async () => {
42
+ const content1 = "Hello, world!";
43
+ const content2 = "Hello, world?";
44
+
45
+ const similarity = await ContentSimilarity.calculateSimilarity(
46
+ content1,
47
+ content2
48
+ );
49
+
50
+ expect(similarity).toBeGreaterThan(0.9);
51
+ expect(similarity).toBeLessThan(1.0);
52
+ });
53
+
54
+ it("should return low similarity for very different content", async () => {
55
+ const content1 = "Hello, world!";
56
+ const content2 = "Goodbye, universe!";
57
+
58
+ const similarity = await ContentSimilarity.calculateSimilarity(
59
+ content1,
60
+ content2
61
+ );
62
+
63
+ expect(similarity).toBeLessThan(0.5);
64
+ });
65
+
66
+ it("should handle mixed string and binary content", async () => {
67
+ const content1 = "Hello, world!";
68
+ const content2 = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]); // "Hello" in ASCII
69
+
70
+ const similarity = await ContentSimilarity.calculateSimilarity(
71
+ content1,
72
+ content2
73
+ );
74
+
75
+ // Mixed content types (string vs binary) should have low similarity
76
+ // since binary is converted to hex representation for comparison
77
+ expect(similarity).toBe(0.0);
78
+ });
79
+
80
+ it("should use sampling for large content", async () => {
81
+ const content1 =
82
+ "a".repeat(10000) + "different middle" + "b".repeat(10000);
83
+ const content2 =
84
+ "a".repeat(10000) + "same middle here" + "b".repeat(10000);
85
+
86
+ const similarity = await ContentSimilarity.calculateSimilarity(
87
+ content1,
88
+ content2
89
+ );
90
+
91
+ // Should still detect high similarity due to matching beginning and end
92
+ expect(similarity).toBeGreaterThan(0.6);
93
+ });
94
+
95
+ it("should handle empty content", async () => {
96
+ const content1 = "";
97
+ const content2 = "not empty";
98
+
99
+ const similarity = await ContentSimilarity.calculateSimilarity(
100
+ content1,
101
+ content2
102
+ );
103
+
104
+ expect(similarity).toBe(0.0);
105
+ });
106
+
107
+ it("should return 1.0 for both empty content", async () => {
108
+ const content1 = "";
109
+ const content2 = "";
110
+
111
+ const similarity = await ContentSimilarity.calculateSimilarity(
112
+ content1,
113
+ content2
114
+ );
115
+
116
+ expect(similarity).toBe(1.0);
117
+ });
118
+ });
119
+
120
+ describe("getConfidenceLevel", () => {
121
+ it("should return auto for high similarity", () => {
122
+ expect(ContentSimilarity.getConfidenceLevel(0.9)).toBe("auto");
123
+ expect(ContentSimilarity.getConfidenceLevel(0.8)).toBe("auto");
124
+ });
125
+
126
+ it("should return prompt for medium similarity", () => {
127
+ expect(ContentSimilarity.getConfidenceLevel(0.7)).toBe("prompt");
128
+ expect(ContentSimilarity.getConfidenceLevel(0.5)).toBe("prompt");
129
+ });
130
+
131
+ it("should return low for low similarity", () => {
132
+ expect(ContentSimilarity.getConfidenceLevel(0.4)).toBe("low");
133
+ expect(ContentSimilarity.getConfidenceLevel(0.0)).toBe("low");
134
+ });
135
+ });
136
+
137
+ describe("shouldAutoApply", () => {
138
+ it("should return true for high similarity", () => {
139
+ expect(ContentSimilarity.shouldAutoApply(0.9)).toBe(true);
140
+ expect(ContentSimilarity.shouldAutoApply(0.8)).toBe(true);
141
+ });
142
+
143
+ it("should return false for medium/low similarity", () => {
144
+ expect(ContentSimilarity.shouldAutoApply(0.7)).toBe(false);
145
+ expect(ContentSimilarity.shouldAutoApply(0.5)).toBe(false);
146
+ expect(ContentSimilarity.shouldAutoApply(0.3)).toBe(false);
147
+ });
148
+ });
149
+
150
+ describe("shouldPromptUser", () => {
151
+ it("should return true for medium similarity", () => {
152
+ expect(ContentSimilarity.shouldPromptUser(0.7)).toBe(true);
153
+ expect(ContentSimilarity.shouldPromptUser(0.6)).toBe(true);
154
+ expect(ContentSimilarity.shouldPromptUser(0.5)).toBe(true);
155
+ });
156
+
157
+ it("should return false for high similarity", () => {
158
+ expect(ContentSimilarity.shouldPromptUser(0.9)).toBe(false);
159
+ expect(ContentSimilarity.shouldPromptUser(0.8)).toBe(false);
160
+ });
161
+
162
+ it("should return false for low similarity", () => {
163
+ expect(ContentSimilarity.shouldPromptUser(0.4)).toBe(false);
164
+ expect(ContentSimilarity.shouldPromptUser(0.3)).toBe(false);
165
+ });
166
+ });
167
+
168
+ describe("edge cases", () => {
169
+ it("should handle Unicode content correctly", async () => {
170
+ const content1 = "🚀 Hello, 世界!";
171
+ const content2 = "🚀 Hello, 世界?";
172
+
173
+ const similarity = await ContentSimilarity.calculateSimilarity(
174
+ content1,
175
+ content2
176
+ );
177
+
178
+ expect(similarity).toBeGreaterThan(0.9);
179
+ });
180
+
181
+ it("should handle line breaks and whitespace", async () => {
182
+ const content1 = "Line 1\nLine 2\nLine 3";
183
+ const content2 = "Line 1\r\nLine 2\r\nLine 3";
184
+
185
+ const similarity = await ContentSimilarity.calculateSimilarity(
186
+ content1,
187
+ content2
188
+ );
189
+
190
+ expect(similarity).toBeGreaterThan(0.8);
191
+ });
192
+
193
+ it("should handle very small content differences", async () => {
194
+ const content1 = "a";
195
+ const content2 = "b";
196
+
197
+ const similarity = await ContentSimilarity.calculateSimilarity(
198
+ content1,
199
+ content2
200
+ );
201
+
202
+ expect(similarity).toBe(0.0); // Single character, completely different
203
+ });
204
+
205
+ it("should handle binary data with patterns", async () => {
206
+ const content1 = new Uint8Array([0x00, 0x01, 0x02, 0x03, 0x04, 0x05]);
207
+ const content2 = new Uint8Array([0x00, 0x01, 0x02, 0xff, 0x04, 0x05]);
208
+
209
+ const similarity = await ContentSimilarity.calculateSimilarity(
210
+ content1,
211
+ content2
212
+ );
213
+
214
+ expect(similarity).toBeGreaterThan(0.5); // Most bytes are the same
215
+ expect(similarity).toBeLessThan(1.0);
216
+ });
217
+ });
218
+
219
+ describe("performance characteristics", () => {
220
+ it("should handle reasonably large files efficiently", async () => {
221
+ const size = 100000; // 100KB
222
+ const content1 = "a".repeat(size);
223
+ const content2 = "a".repeat(size - 10) + "b".repeat(10);
224
+
225
+ const startTime = Date.now();
226
+ const similarity = await ContentSimilarity.calculateSimilarity(
227
+ content1,
228
+ content2
229
+ );
230
+ const duration = Date.now() - startTime;
231
+
232
+ expect(similarity).toBeGreaterThan(0.8);
233
+ expect(duration).toBeLessThan(1000); // Should complete within 1 second
234
+ }, 10000); // 10 second timeout for this test
235
+ });
236
+ });
@@ -0,0 +1,260 @@
1
+ import * as fs from "fs/promises";
2
+ import * as path from "path";
3
+ import { tmpdir } from "os";
4
+ import {
5
+ readFileContent,
6
+ writeFileContent,
7
+ removePath,
8
+ pathExists,
9
+ } from "../../src/utils";
10
+ import { SnapshotManager } from "../../src/core/snapshot";
11
+ import { ChangeDetector } from "../../src/core/change-detection";
12
+ import { ChangeType } from "../../src/types";
13
+
14
+ describe("File Deletion Behavior", () => {
15
+ let testDir: string;
16
+ let snapshotManager: SnapshotManager;
17
+ let changeDetector: ChangeDetector;
18
+
19
+ beforeEach(async () => {
20
+ testDir = await fs.mkdtemp(path.join(tmpdir(), "deletion-test-"));
21
+ snapshotManager = new SnapshotManager(testDir);
22
+ // Create a minimal change detector for testing (without Automerge repo)
23
+ changeDetector = new ChangeDetector(null as any, testDir, []);
24
+ });
25
+
26
+ afterEach(async () => {
27
+ await fs.rm(testDir, { recursive: true, force: true });
28
+ });
29
+
30
+ describe("Basic File Deletion", () => {
31
+ it("should read TypeScript files correctly before deletion", async () => {
32
+ const tsFile = path.join(testDir, "component.ts");
33
+ const content = "interface User { name: string; }";
34
+ await writeFileContent(tsFile, content);
35
+
36
+ const result = await readFileContent(tsFile);
37
+ expect(typeof result).toBe("string");
38
+ expect(result).toBe(content);
39
+ });
40
+
41
+ it("should handle file deletion through removePath", async () => {
42
+ const filePath = path.join(testDir, "test.txt");
43
+ await writeFileContent(filePath, "test content");
44
+
45
+ expect(await pathExists(filePath)).toBe(true);
46
+
47
+ await removePath(filePath);
48
+
49
+ expect(await pathExists(filePath)).toBe(false);
50
+ });
51
+
52
+ it("should handle directory deletion through removePath", async () => {
53
+ const dirPath = path.join(testDir, "subdir");
54
+ const filePath = path.join(dirPath, "file.txt");
55
+
56
+ await fs.mkdir(dirPath);
57
+ await writeFileContent(filePath, "content");
58
+
59
+ expect(await pathExists(dirPath)).toBe(true);
60
+ expect(await pathExists(filePath)).toBe(true);
61
+
62
+ await removePath(dirPath);
63
+
64
+ expect(await pathExists(dirPath)).toBe(false);
65
+ expect(await pathExists(filePath)).toBe(false);
66
+ });
67
+ });
68
+
69
+ describe("Snapshot Deletion Behavior", () => {
70
+ it("should properly remove files from snapshot", () => {
71
+ const snapshot = snapshotManager.createEmpty();
72
+
73
+ // Add a file to snapshot
74
+ snapshotManager.updateFileEntry(snapshot, "test.txt", {
75
+ path: path.join(testDir, "test.txt"),
76
+ url: "automerge:test-url" as any,
77
+ head: ["test-head"] as any,
78
+ extension: "txt",
79
+ mimeType: "text/plain",
80
+ });
81
+
82
+ expect(snapshot.files.has("test.txt")).toBe(true);
83
+
84
+ // Remove file from snapshot
85
+ snapshotManager.removeFileEntry(snapshot, "test.txt");
86
+
87
+ expect(snapshot.files.has("test.txt")).toBe(false);
88
+ expect(snapshot.files.size).toBe(0);
89
+ });
90
+
91
+ it("should handle removing non-existent files gracefully", () => {
92
+ const snapshot = snapshotManager.createEmpty();
93
+
94
+ // Should not throw when removing non-existent file
95
+ expect(() => {
96
+ snapshotManager.removeFileEntry(snapshot, "nonexistent.txt");
97
+ }).not.toThrow();
98
+
99
+ expect(snapshot.files.size).toBe(0);
100
+ });
101
+ });
102
+
103
+ describe("Deletion Scenario Simulation", () => {
104
+ it("should simulate local file deletion scenario", async () => {
105
+ // Create a file
106
+ const filePath = path.join(testDir, "deleteme.txt");
107
+ const content = "This file will be deleted";
108
+ await writeFileContent(filePath, content);
109
+
110
+ // Verify file exists
111
+ expect(await pathExists(filePath)).toBe(true);
112
+ const readContent = await readFileContent(filePath);
113
+ expect(readContent).toBe(content);
114
+
115
+ // Create snapshot with this file
116
+ const snapshot = snapshotManager.createEmpty();
117
+ snapshotManager.updateFileEntry(snapshot, "deleteme.txt", {
118
+ path: filePath,
119
+ url: "automerge:delete-test" as any,
120
+ head: ["initial-head"] as any,
121
+ extension: "txt",
122
+ mimeType: "text/plain",
123
+ });
124
+
125
+ // Simulate local deletion (user deletes file)
126
+ await removePath(filePath);
127
+
128
+ // Verify file is gone
129
+ expect(await pathExists(filePath)).toBe(false);
130
+
131
+ // Snapshot should still have the file entry (until sync processes the deletion)
132
+ expect(snapshot.files.has("deleteme.txt")).toBe(true);
133
+
134
+ // Simulate sync engine processing the deletion
135
+ snapshotManager.removeFileEntry(snapshot, "deleteme.txt");
136
+
137
+ // Now snapshot should not have the file
138
+ expect(snapshot.files.has("deleteme.txt")).toBe(false);
139
+ });
140
+
141
+ it("should handle rapid create-delete cycles", async () => {
142
+ const filePath = path.join(testDir, "rapid.txt");
143
+
144
+ // Rapid create-delete cycle
145
+ for (let i = 0; i < 5; i++) {
146
+ await writeFileContent(filePath, `content ${i}`);
147
+ expect(await pathExists(filePath)).toBe(true);
148
+
149
+ await removePath(filePath);
150
+ expect(await pathExists(filePath)).toBe(false);
151
+ }
152
+ });
153
+
154
+ it("should handle deletion of different file types", async () => {
155
+ const testFiles = [
156
+ { name: "text.txt", content: "text content" },
157
+ { name: "code.ts", content: "interface Test { x: number; }" },
158
+ { name: "config.json", content: '{"key": "value"}' },
159
+ { name: "binary.bin", content: new Uint8Array([0x00, 0x01, 0x02]) },
160
+ ];
161
+
162
+ // Create all files
163
+ for (const file of testFiles) {
164
+ const filePath = path.join(testDir, file.name);
165
+ await writeFileContent(filePath, file.content);
166
+ expect(await pathExists(filePath)).toBe(true);
167
+ }
168
+
169
+ // Delete all files
170
+ for (const file of testFiles) {
171
+ const filePath = path.join(testDir, file.name);
172
+ await removePath(filePath);
173
+ expect(await pathExists(filePath)).toBe(false);
174
+ }
175
+ });
176
+ });
177
+
178
+ describe("Edge Cases and Error Conditions", () => {
179
+ it("should handle deletion of files with special characters", async () => {
180
+ const specialFiles = [
181
+ "file with spaces.txt",
182
+ "file-with-dashes.txt",
183
+ "file_with_underscores.txt",
184
+ "file.with.multiple.dots.txt",
185
+ ];
186
+
187
+ for (const fileName of specialFiles) {
188
+ const filePath = path.join(testDir, fileName);
189
+ await writeFileContent(filePath, "test content");
190
+ expect(await pathExists(filePath)).toBe(true);
191
+
192
+ await removePath(filePath);
193
+ expect(await pathExists(filePath)).toBe(false);
194
+ }
195
+ });
196
+
197
+ it("should handle deletion of nested directory structures", async () => {
198
+ // Create nested structure
199
+ const nestedPath = path.join(testDir, "level1", "level2", "level3");
200
+ const filePath = path.join(nestedPath, "deep.txt");
201
+
202
+ await fs.mkdir(nestedPath, { recursive: true });
203
+ await writeFileContent(filePath, "deep content");
204
+
205
+ expect(await pathExists(filePath)).toBe(true);
206
+
207
+ // Delete entire structure from top level
208
+ await removePath(path.join(testDir, "level1"));
209
+
210
+ expect(await pathExists(path.join(testDir, "level1"))).toBe(false);
211
+ expect(await pathExists(filePath)).toBe(false);
212
+ });
213
+
214
+ it("should handle concurrent deletion attempts", async () => {
215
+ const filePath = path.join(testDir, "concurrent.txt");
216
+ await writeFileContent(filePath, "content");
217
+
218
+ // Multiple deletion attempts (should not cause errors)
219
+ const deletions = [
220
+ removePath(filePath),
221
+ removePath(filePath),
222
+ removePath(filePath),
223
+ ];
224
+
225
+ await Promise.all(deletions);
226
+
227
+ expect(await pathExists(filePath)).toBe(false);
228
+ });
229
+ });
230
+
231
+ describe("Debug Information", () => {
232
+ it("should provide detailed info about deletion behavior", async () => {
233
+ console.log("\n=== Deletion Behavior Debug Info ===");
234
+
235
+ const filePath = path.join(testDir, "debug.txt");
236
+ const content = "Debug test content";
237
+
238
+ console.log(`Test directory: ${testDir}`);
239
+ console.log(`File path: ${filePath}`);
240
+
241
+ // Create file
242
+ await writeFileContent(filePath, content);
243
+ console.log(`✅ File created successfully`);
244
+
245
+ // Verify file content
246
+ const readBack = await readFileContent(filePath);
247
+ console.log(`✅ File content verified: "${readBack}"`);
248
+
249
+ // Delete file
250
+ await removePath(filePath);
251
+ console.log(`✅ File deleted successfully`);
252
+
253
+ // Verify deletion
254
+ const exists = await pathExists(filePath);
255
+ console.log(`✅ File deletion verified: exists=${exists}`);
256
+
257
+ console.log("=== End Debug Info ===\n");
258
+ });
259
+ });
260
+ });