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.
- package/README.md +460 -0
- package/dist/browser/browser-sync-engine.d.ts +64 -0
- package/dist/browser/browser-sync-engine.d.ts.map +1 -0
- package/dist/browser/browser-sync-engine.js +303 -0
- package/dist/browser/browser-sync-engine.js.map +1 -0
- package/dist/browser/filesystem-adapter.d.ts +84 -0
- package/dist/browser/filesystem-adapter.d.ts.map +1 -0
- package/dist/browser/filesystem-adapter.js +413 -0
- package/dist/browser/filesystem-adapter.js.map +1 -0
- package/dist/browser/index.d.ts +36 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +90 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/types.d.ts +70 -0
- package/dist/browser/types.d.ts.map +1 -0
- package/dist/browser/types.js +6 -0
- package/dist/browser/types.js.map +1 -0
- package/dist/cli/commands.d.ts +71 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/cli/commands.js +794 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +19 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +199 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/index.d.ts +71 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +314 -0
- package/dist/config/index.js.map +1 -0
- package/dist/core/change-detection.d.ts +78 -0
- package/dist/core/change-detection.d.ts.map +1 -0
- package/dist/core/change-detection.js +370 -0
- package/dist/core/change-detection.js.map +1 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +22 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/isomorphic-snapshot.d.ts +58 -0
- package/dist/core/isomorphic-snapshot.d.ts.map +1 -0
- package/dist/core/isomorphic-snapshot.js +204 -0
- package/dist/core/isomorphic-snapshot.js.map +1 -0
- package/dist/core/move-detection.d.ts +72 -0
- package/dist/core/move-detection.d.ts.map +1 -0
- package/dist/core/move-detection.js +200 -0
- package/dist/core/move-detection.js.map +1 -0
- package/dist/core/snapshot.d.ts +109 -0
- package/dist/core/snapshot.d.ts.map +1 -0
- package/dist/core/snapshot.js +263 -0
- package/dist/core/snapshot.js.map +1 -0
- package/dist/core/sync-engine.d.ts +110 -0
- package/dist/core/sync-engine.d.ts.map +1 -0
- package/dist/core/sync-engine.js +817 -0
- package/dist/core/sync-engine.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/platform/browser-filesystem.d.ts +26 -0
- package/dist/platform/browser-filesystem.d.ts.map +1 -0
- package/dist/platform/browser-filesystem.js +91 -0
- package/dist/platform/browser-filesystem.js.map +1 -0
- package/dist/platform/filesystem.d.ts +29 -0
- package/dist/platform/filesystem.d.ts.map +1 -0
- package/dist/platform/filesystem.js +65 -0
- package/dist/platform/filesystem.js.map +1 -0
- package/dist/platform/node-filesystem.d.ts +21 -0
- package/dist/platform/node-filesystem.d.ts.map +1 -0
- package/dist/platform/node-filesystem.js +93 -0
- package/dist/platform/node-filesystem.js.map +1 -0
- package/dist/types/config.d.ts +119 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +3 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/documents.d.ts +70 -0
- package/dist/types/documents.d.ts.map +1 -0
- package/dist/types/documents.js +23 -0
- package/dist/types/documents.js.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +23 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/snapshot.d.ts +81 -0
- package/dist/types/snapshot.d.ts.map +1 -0
- package/dist/types/snapshot.js +17 -0
- package/dist/types/snapshot.js.map +1 -0
- package/dist/utils/content-similarity.d.ts +53 -0
- package/dist/utils/content-similarity.d.ts.map +1 -0
- package/dist/utils/content-similarity.js +155 -0
- package/dist/utils/content-similarity.js.map +1 -0
- package/dist/utils/content.d.ts +5 -0
- package/dist/utils/content.d.ts.map +1 -0
- package/dist/utils/content.js +30 -0
- package/dist/utils/content.js.map +1 -0
- package/dist/utils/fs-browser.d.ts +57 -0
- package/dist/utils/fs-browser.d.ts.map +1 -0
- package/dist/utils/fs-browser.js +311 -0
- package/dist/utils/fs-browser.js.map +1 -0
- package/dist/utils/fs-node.d.ts +53 -0
- package/dist/utils/fs-node.d.ts.map +1 -0
- package/dist/utils/fs-node.js +220 -0
- package/dist/utils/fs-node.js.map +1 -0
- package/dist/utils/fs.d.ts +62 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +293 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +23 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/isomorphic.d.ts +29 -0
- package/dist/utils/isomorphic.d.ts.map +1 -0
- package/dist/utils/isomorphic.js +139 -0
- package/dist/utils/isomorphic.js.map +1 -0
- package/dist/utils/mime-types.d.ts +13 -0
- package/dist/utils/mime-types.d.ts.map +1 -0
- package/dist/utils/mime-types.js +240 -0
- package/dist/utils/mime-types.js.map +1 -0
- package/dist/utils/network-sync.d.ts +12 -0
- package/dist/utils/network-sync.d.ts.map +1 -0
- package/dist/utils/network-sync.js +149 -0
- package/dist/utils/network-sync.js.map +1 -0
- package/dist/utils/pure.d.ts +25 -0
- package/dist/utils/pure.d.ts.map +1 -0
- package/dist/utils/pure.js +112 -0
- package/dist/utils/pure.js.map +1 -0
- package/dist/utils/repo-factory.d.ts +11 -0
- package/dist/utils/repo-factory.d.ts.map +1 -0
- package/dist/utils/repo-factory.js +77 -0
- package/dist/utils/repo-factory.js.map +1 -0
- package/package.json +83 -0
- package/src/cli/commands.ts +1053 -0
- package/src/cli/index.ts +2 -0
- package/src/cli.ts +287 -0
- package/src/config/index.ts +334 -0
- package/src/core/change-detection.ts +484 -0
- package/src/core/index.ts +5 -0
- package/src/core/move-detection.ts +269 -0
- package/src/core/snapshot.ts +285 -0
- package/src/core/sync-engine.ts +1167 -0
- package/src/index.ts +14 -0
- package/src/types/config.ts +130 -0
- package/src/types/documents.ts +72 -0
- package/src/types/index.ts +8 -0
- package/src/types/snapshot.ts +88 -0
- package/src/utils/content-similarity.ts +194 -0
- package/src/utils/content.ts +28 -0
- package/src/utils/fs.ts +289 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/mime-types.ts +236 -0
- package/src/utils/network-sync.ts +153 -0
- package/src/utils/repo-factory.ts +58 -0
- package/test/README-TESTING-GAPS.md +174 -0
- package/test/integration/README.md +328 -0
- package/test/integration/clone-test.sh +310 -0
- package/test/integration/conflict-resolution-test.sh +309 -0
- package/test/integration/deletion-behavior-test.sh +487 -0
- package/test/integration/deletion-sync-test-simple.sh +193 -0
- package/test/integration/deletion-sync-test.sh +297 -0
- package/test/integration/exclude-patterns.test.ts +152 -0
- package/test/integration/full-integration-test.sh +363 -0
- package/test/integration/sync-deletion.test.ts +339 -0
- package/test/integration/sync-flow.test.ts +309 -0
- package/test/run-tests.sh +225 -0
- package/test/unit/content-similarity.test.ts +236 -0
- package/test/unit/deletion-behavior.test.ts +260 -0
- package/test/unit/enhanced-mime-detection.test.ts +266 -0
- package/test/unit/snapshot.test.ts +431 -0
- package/test/unit/sync-timing.test.ts +178 -0
- package/test/unit/utils.test.ts +368 -0
- package/tools/browser-sync/README.md +116 -0
- package/tools/browser-sync/package.json +44 -0
- package/tools/browser-sync/patchwork.json +1 -0
- package/tools/browser-sync/pnpm-lock.yaml +4202 -0
- package/tools/browser-sync/src/components/BrowserSyncTool.tsx +599 -0
- package/tools/browser-sync/src/index.ts +20 -0
- package/tools/browser-sync/src/polyfills.ts +31 -0
- package/tools/browser-sync/src/styles.css +290 -0
- package/tools/browser-sync/src/types.ts +27 -0
- package/tools/browser-sync/vite.config.ts +25 -0
- 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
|
+
});
|