pushwork 1.0.4 → 1.0.7
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 +87 -328
- package/dist/.pushwork/automerge/3P/Dm3ekE2pmjGnWvDaG3vSR7ww98/snapshot/aa2349c94955ea561f698720142f9d884a6872d9f82dc332d578c216beb0df0e +0 -0
- package/dist/.pushwork/automerge/st/orage-adapter-id +1 -0
- package/dist/.pushwork/config.json +15 -0
- package/dist/.pushwork/snapshot.json +7 -0
- package/dist/cli.js +231 -170
- package/dist/cli.js.map +1 -1
- package/dist/commands.d.ts +51 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +799 -0
- package/dist/commands.js.map +1 -0
- package/dist/core/change-detection.d.ts +6 -19
- package/dist/core/change-detection.d.ts.map +1 -1
- package/dist/core/change-detection.js +101 -80
- package/dist/core/change-detection.js.map +1 -1
- package/dist/{config/index.d.ts → core/config.d.ts} +13 -3
- package/dist/core/config.d.ts.map +1 -0
- package/dist/{config/index.js → core/config.js} +55 -73
- package/dist/core/config.js.map +1 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/move-detection.d.ts +12 -50
- package/dist/core/move-detection.d.ts.map +1 -1
- package/dist/core/move-detection.js +58 -139
- package/dist/core/move-detection.js.map +1 -1
- package/dist/core/snapshot.d.ts +0 -4
- package/dist/core/snapshot.d.ts.map +1 -1
- package/dist/core/snapshot.js +2 -11
- package/dist/core/snapshot.js.map +1 -1
- package/dist/core/sync-engine.d.ts +5 -11
- package/dist/core/sync-engine.d.ts.map +1 -1
- package/dist/core/sync-engine.js +220 -362
- package/dist/core/sync-engine.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -6
- package/dist/index.js.map +1 -1
- package/dist/types/config.d.ts +43 -67
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +6 -0
- package/dist/types/config.js.map +1 -1
- package/dist/types/documents.d.ts +15 -3
- package/dist/types/documents.d.ts.map +1 -1
- package/dist/types/documents.js.map +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +0 -3
- package/dist/types/index.js.map +1 -1
- package/dist/types/snapshot.d.ts +3 -21
- package/dist/types/snapshot.d.ts.map +1 -1
- package/dist/types/snapshot.js +0 -14
- package/dist/types/snapshot.js.map +1 -1
- package/dist/utils/content.d.ts.map +1 -1
- package/dist/utils/content.js +2 -6
- package/dist/utils/content.js.map +1 -1
- package/dist/utils/directory.d.ts +10 -0
- package/dist/utils/directory.d.ts.map +1 -0
- package/dist/utils/directory.js +37 -0
- package/dist/utils/directory.js.map +1 -0
- package/dist/utils/fs.d.ts +15 -2
- package/dist/utils/fs.d.ts.map +1 -1
- package/dist/utils/fs.js +63 -53
- package/dist/utils/fs.js.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -4
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/mime-types.d.ts.map +1 -1
- package/dist/utils/mime-types.js +11 -4
- package/dist/utils/mime-types.js.map +1 -1
- package/dist/utils/network-sync.d.ts +0 -6
- package/dist/utils/network-sync.d.ts.map +1 -1
- package/dist/utils/network-sync.js +55 -99
- package/dist/utils/network-sync.js.map +1 -1
- package/dist/utils/output.d.ts +129 -0
- package/dist/utils/output.d.ts.map +1 -0
- package/dist/utils/output.js +375 -0
- package/dist/utils/output.js.map +1 -0
- package/dist/utils/repo-factory.d.ts +2 -6
- package/dist/utils/repo-factory.d.ts.map +1 -1
- package/dist/utils/repo-factory.js +8 -22
- package/dist/utils/repo-factory.js.map +1 -1
- package/dist/utils/string-similarity.d.ts +14 -0
- package/dist/utils/string-similarity.d.ts.map +1 -0
- package/dist/utils/string-similarity.js +43 -0
- package/dist/utils/string-similarity.js.map +1 -0
- package/dist/utils/trace.d.ts +19 -0
- package/dist/utils/trace.d.ts.map +1 -0
- package/dist/utils/trace.js +68 -0
- package/dist/utils/trace.js.map +1 -0
- package/package.json +17 -12
- package/src/cli.ts +326 -252
- package/src/commands.ts +988 -0
- package/src/core/change-detection.ts +199 -162
- package/src/{config/index.ts → core/config.ts} +65 -82
- package/src/core/index.ts +1 -1
- package/src/core/move-detection.ts +74 -180
- package/src/core/snapshot.ts +2 -12
- package/src/core/sync-engine.ts +248 -499
- package/src/index.ts +0 -10
- package/src/types/config.ts +50 -72
- package/src/types/documents.ts +16 -3
- package/src/types/index.ts +0 -5
- package/src/types/snapshot.ts +1 -23
- package/src/utils/content.ts +2 -6
- package/src/utils/directory.ts +50 -0
- package/src/utils/fs.ts +67 -56
- package/src/utils/index.ts +1 -6
- package/src/utils/mime-types.ts +12 -4
- package/src/utils/network-sync.ts +79 -137
- package/src/utils/output.ts +450 -0
- package/src/utils/repo-factory.ts +13 -31
- package/src/utils/string-similarity.ts +54 -0
- package/src/utils/trace.ts +70 -0
- package/test/integration/exclude-patterns.test.ts +6 -15
- package/test/integration/fuzzer.test.ts +308 -391
- package/test/integration/init-sync.test.ts +89 -0
- package/test/integration/sync-deletion.test.ts +2 -61
- package/test/integration/sync-flow.test.ts +4 -24
- package/test/jest.setup.ts +34 -0
- package/test/unit/deletion-behavior.test.ts +3 -14
- package/test/unit/enhanced-mime-detection.test.ts +0 -22
- package/test/unit/snapshot.test.ts +2 -29
- package/test/unit/sync-convergence.test.ts +3 -198
- package/test/unit/sync-timing.test.ts +0 -44
- package/test/unit/utils.test.ts +0 -2
- package/tsconfig.json +3 -3
- package/dist/browser/browser-sync-engine.d.ts +0 -64
- package/dist/browser/browser-sync-engine.d.ts.map +0 -1
- package/dist/browser/browser-sync-engine.js +0 -303
- package/dist/browser/browser-sync-engine.js.map +0 -1
- package/dist/browser/filesystem-adapter.d.ts +0 -84
- package/dist/browser/filesystem-adapter.d.ts.map +0 -1
- package/dist/browser/filesystem-adapter.js +0 -413
- package/dist/browser/filesystem-adapter.js.map +0 -1
- package/dist/browser/index.d.ts +0 -36
- package/dist/browser/index.d.ts.map +0 -1
- package/dist/browser/index.js +0 -90
- package/dist/browser/index.js.map +0 -1
- package/dist/browser/types.d.ts +0 -70
- package/dist/browser/types.d.ts.map +0 -1
- package/dist/browser/types.js +0 -6
- package/dist/browser/types.js.map +0 -1
- package/dist/cli/commands.d.ts +0 -77
- package/dist/cli/commands.d.ts.map +0 -1
- package/dist/cli/commands.js +0 -904
- package/dist/cli/commands.js.map +0 -1
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -19
- package/dist/cli/index.js.map +0 -1
- package/dist/config/index.d.ts.map +0 -1
- package/dist/config/index.js.map +0 -1
- package/dist/core/isomorphic-snapshot.d.ts +0 -58
- package/dist/core/isomorphic-snapshot.d.ts.map +0 -1
- package/dist/core/isomorphic-snapshot.js +0 -204
- package/dist/core/isomorphic-snapshot.js.map +0 -1
- package/dist/platform/browser-filesystem.d.ts +0 -26
- package/dist/platform/browser-filesystem.d.ts.map +0 -1
- package/dist/platform/browser-filesystem.js +0 -91
- package/dist/platform/browser-filesystem.js.map +0 -1
- package/dist/platform/filesystem.d.ts +0 -29
- package/dist/platform/filesystem.d.ts.map +0 -1
- package/dist/platform/filesystem.js +0 -65
- package/dist/platform/filesystem.js.map +0 -1
- package/dist/platform/node-filesystem.d.ts +0 -21
- package/dist/platform/node-filesystem.d.ts.map +0 -1
- package/dist/platform/node-filesystem.js +0 -93
- package/dist/platform/node-filesystem.js.map +0 -1
- package/dist/utils/content-similarity.d.ts +0 -53
- package/dist/utils/content-similarity.d.ts.map +0 -1
- package/dist/utils/content-similarity.js +0 -155
- package/dist/utils/content-similarity.js.map +0 -1
- package/dist/utils/fs-browser.d.ts +0 -57
- package/dist/utils/fs-browser.d.ts.map +0 -1
- package/dist/utils/fs-browser.js +0 -311
- package/dist/utils/fs-browser.js.map +0 -1
- package/dist/utils/fs-node.d.ts +0 -53
- package/dist/utils/fs-node.d.ts.map +0 -1
- package/dist/utils/fs-node.js +0 -220
- package/dist/utils/fs-node.js.map +0 -1
- package/dist/utils/isomorphic.d.ts +0 -29
- package/dist/utils/isomorphic.d.ts.map +0 -1
- package/dist/utils/isomorphic.js +0 -139
- package/dist/utils/isomorphic.js.map +0 -1
- package/dist/utils/pure.d.ts +0 -25
- package/dist/utils/pure.d.ts.map +0 -1
- package/dist/utils/pure.js +0 -112
- package/dist/utils/pure.js.map +0 -1
- package/src/cli/commands.ts +0 -1207
- package/src/cli/index.ts +0 -2
- package/src/utils/content-similarity.ts +0 -194
- package/test/README-TESTING-GAPS.md +0 -174
- package/test/unit/content-similarity.test.ts +0 -236
package/src/cli/index.ts
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import { calculateContentHash } from "./fs";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Content similarity calculation for move detection
|
|
5
|
-
*/
|
|
6
|
-
export class ContentSimilarity {
|
|
7
|
-
private static readonly CHUNK_SIZE = 1024; // 1KB chunks for sampling
|
|
8
|
-
private static readonly AUTO_THRESHOLD = 0.8;
|
|
9
|
-
private static readonly PROMPT_THRESHOLD = 0.5;
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Calculate similarity between two content pieces
|
|
13
|
-
*/
|
|
14
|
-
static async calculateSimilarity(
|
|
15
|
-
content1: string | Uint8Array,
|
|
16
|
-
content2: string | Uint8Array
|
|
17
|
-
): Promise<number> {
|
|
18
|
-
// Quick early exit for identical content
|
|
19
|
-
if (await this.areIdentical(content1, content2)) {
|
|
20
|
-
return 1.0;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Size-based quick rejection
|
|
24
|
-
const size1 =
|
|
25
|
-
typeof content1 === "string" ? content1.length : content1.length;
|
|
26
|
-
const size2 =
|
|
27
|
-
typeof content2 === "string" ? content2.length : content2.length;
|
|
28
|
-
const sizeDiff = Math.abs(size1 - size2) / Math.max(size1, size2);
|
|
29
|
-
|
|
30
|
-
if (sizeDiff > 0.5) {
|
|
31
|
-
return 0.0; // Too different in size
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// For small files, use full content comparison
|
|
35
|
-
if (size1 < this.CHUNK_SIZE * 4 && size2 < this.CHUNK_SIZE * 4) {
|
|
36
|
-
return this.calculateFullSimilarity(content1, content2);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// For large files, use sampling
|
|
40
|
-
return this.calculateSampledSimilarity(content1, content2);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Check if two content pieces are identical
|
|
45
|
-
*/
|
|
46
|
-
private static async areIdentical(
|
|
47
|
-
content1: string | Uint8Array,
|
|
48
|
-
content2: string | Uint8Array
|
|
49
|
-
): Promise<boolean> {
|
|
50
|
-
const hash1 = await calculateContentHash(content1);
|
|
51
|
-
const hash2 = await calculateContentHash(content2);
|
|
52
|
-
return hash1 === hash2;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Calculate similarity for small files using full content
|
|
57
|
-
*/
|
|
58
|
-
private static calculateFullSimilarity(
|
|
59
|
-
content1: string | Uint8Array,
|
|
60
|
-
content2: string | Uint8Array
|
|
61
|
-
): number {
|
|
62
|
-
const str1 =
|
|
63
|
-
typeof content1 === "string" ? content1 : this.bufferToString(content1);
|
|
64
|
-
const str2 =
|
|
65
|
-
typeof content2 === "string" ? content2 : this.bufferToString(content2);
|
|
66
|
-
|
|
67
|
-
return this.levenshteinSimilarity(str1, str2);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Calculate similarity for large files using sampling
|
|
72
|
-
*/
|
|
73
|
-
private static calculateSampledSimilarity(
|
|
74
|
-
content1: string | Uint8Array,
|
|
75
|
-
content2: string | Uint8Array
|
|
76
|
-
): number {
|
|
77
|
-
const samples1 = this.getSamples(content1);
|
|
78
|
-
const samples2 = this.getSamples(content2);
|
|
79
|
-
|
|
80
|
-
let totalSimilarity = 0;
|
|
81
|
-
let comparisons = 0;
|
|
82
|
-
|
|
83
|
-
for (let i = 0; i < Math.min(samples1.length, samples2.length); i++) {
|
|
84
|
-
totalSimilarity += this.levenshteinSimilarity(samples1[i], samples2[i]);
|
|
85
|
-
comparisons++;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return comparisons > 0 ? totalSimilarity / comparisons : 0;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Get representative samples from content
|
|
93
|
-
*/
|
|
94
|
-
private static getSamples(content: string | Uint8Array): string[] {
|
|
95
|
-
const str =
|
|
96
|
-
typeof content === "string" ? content : this.bufferToString(content);
|
|
97
|
-
const length = str.length;
|
|
98
|
-
const samples: string[] = [];
|
|
99
|
-
|
|
100
|
-
if (length <= this.CHUNK_SIZE) {
|
|
101
|
-
samples.push(str);
|
|
102
|
-
return samples;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Beginning
|
|
106
|
-
samples.push(str.slice(0, this.CHUNK_SIZE));
|
|
107
|
-
|
|
108
|
-
// Middle
|
|
109
|
-
const midStart = Math.floor(length / 2) - Math.floor(this.CHUNK_SIZE / 2);
|
|
110
|
-
samples.push(str.slice(midStart, midStart + this.CHUNK_SIZE));
|
|
111
|
-
|
|
112
|
-
// End
|
|
113
|
-
samples.push(str.slice(-this.CHUNK_SIZE));
|
|
114
|
-
|
|
115
|
-
return samples;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Calculate Levenshtein similarity (0-1 scale)
|
|
120
|
-
*/
|
|
121
|
-
private static levenshteinSimilarity(str1: string, str2: string): number {
|
|
122
|
-
if (str1 === str2) return 1.0;
|
|
123
|
-
if (str1.length === 0 || str2.length === 0) return 0.0;
|
|
124
|
-
|
|
125
|
-
const distance = this.levenshteinDistance(str1, str2);
|
|
126
|
-
const maxLength = Math.max(str1.length, str2.length);
|
|
127
|
-
|
|
128
|
-
return 1 - distance / maxLength;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Calculate Levenshtein distance
|
|
133
|
-
*/
|
|
134
|
-
private static levenshteinDistance(str1: string, str2: string): number {
|
|
135
|
-
const matrix = Array(str2.length + 1)
|
|
136
|
-
.fill(null)
|
|
137
|
-
.map(() => Array(str1.length + 1).fill(null));
|
|
138
|
-
|
|
139
|
-
for (let i = 0; i <= str1.length; i++) matrix[0][i] = i;
|
|
140
|
-
for (let j = 0; j <= str2.length; j++) matrix[j][0] = j;
|
|
141
|
-
|
|
142
|
-
for (let j = 1; j <= str2.length; j++) {
|
|
143
|
-
for (let i = 1; i <= str1.length; i++) {
|
|
144
|
-
const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1;
|
|
145
|
-
matrix[j][i] = Math.min(
|
|
146
|
-
matrix[j][i - 1] + 1, // deletion
|
|
147
|
-
matrix[j - 1][i] + 1, // insertion
|
|
148
|
-
matrix[j - 1][i - 1] + indicator // substitution
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return matrix[str2.length][str1.length];
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Convert buffer to string for comparison
|
|
158
|
-
*/
|
|
159
|
-
private static bufferToString(buffer: Uint8Array): string {
|
|
160
|
-
// For binary content, use hex representation for comparison
|
|
161
|
-
return Array.from(buffer)
|
|
162
|
-
.map((b) => b.toString(16).padStart(2, "0"))
|
|
163
|
-
.join("");
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Determine confidence level based on similarity score
|
|
168
|
-
*/
|
|
169
|
-
static getConfidenceLevel(similarity: number): "auto" | "prompt" | "low" {
|
|
170
|
-
if (similarity >= this.AUTO_THRESHOLD) {
|
|
171
|
-
return "auto";
|
|
172
|
-
} else if (similarity >= this.PROMPT_THRESHOLD) {
|
|
173
|
-
return "prompt";
|
|
174
|
-
} else {
|
|
175
|
-
return "low";
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Should auto-apply move based on similarity
|
|
181
|
-
*/
|
|
182
|
-
static shouldAutoApply(similarity: number): boolean {
|
|
183
|
-
return similarity >= this.AUTO_THRESHOLD;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Should prompt user for move confirmation
|
|
188
|
-
*/
|
|
189
|
-
static shouldPromptUser(similarity: number): boolean {
|
|
190
|
-
return (
|
|
191
|
-
similarity >= this.PROMPT_THRESHOLD && similarity < this.AUTO_THRESHOLD
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
# Testing Gaps Analysis
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
This document outlines the testing gaps identified in the autosync codebase, particularly around the major architectural changes implemented for directory document management and text CRDT support.
|
|
6
|
-
|
|
7
|
-
## Current Test Coverage
|
|
8
|
-
|
|
9
|
-
✅ **Well Tested:**
|
|
10
|
-
|
|
11
|
-
- File system utilities (`test/unit/utils.test.ts`) - 89 test cases
|
|
12
|
-
- Snapshot management (`test/unit/snapshot.test.ts`) - Comprehensive coverage
|
|
13
|
-
- Content similarity (`test/unit/content-similarity.test.ts`) - Move detection logic
|
|
14
|
-
- Integration scenarios (`test/integration/*.test.ts`) - File operations, exclude patterns
|
|
15
|
-
|
|
16
|
-
## Critical Testing Gaps
|
|
17
|
-
|
|
18
|
-
### 1. SyncEngine Core Logic (HIGH PRIORITY)
|
|
19
|
-
|
|
20
|
-
**Status:** ❌ No direct unit tests
|
|
21
|
-
**Impact:** Core synchronization logic untested
|
|
22
|
-
|
|
23
|
-
**Missing Coverage:**
|
|
24
|
-
|
|
25
|
-
- `sync()` method with various file scenarios
|
|
26
|
-
- `applyLocalChangeToRemote()` / `applyRemoteChangeToLocal()`
|
|
27
|
-
- Bidirectional sync flow
|
|
28
|
-
- Error handling during sync operations
|
|
29
|
-
- Dry run mode functionality
|
|
30
|
-
|
|
31
|
-
### 2. Directory Document Management (HIGH PRIORITY)
|
|
32
|
-
|
|
33
|
-
**Status:** ❌ No tests for new feature
|
|
34
|
-
**Impact:** New architectural feature completely untested
|
|
35
|
-
|
|
36
|
-
**Missing Coverage:**
|
|
37
|
-
|
|
38
|
-
- `addFileToRootDirectory()` - Adding files to directory structure
|
|
39
|
-
- `removeFileFromRootDirectory()` - Removing files from directory structure
|
|
40
|
-
- `setRootDirectoryUrl()` - Root directory URL persistence
|
|
41
|
-
- Directory structure synchronization between peers
|
|
42
|
-
- Root directory document creation during init
|
|
43
|
-
|
|
44
|
-
### 3. Text CRDT Implementation (MEDIUM PRIORITY)
|
|
45
|
-
|
|
46
|
-
**Status:** ❌ No tests for new feature
|
|
47
|
-
**Impact:** New text handling functionality untested
|
|
48
|
-
|
|
49
|
-
**Missing Coverage:**
|
|
50
|
-
|
|
51
|
-
- `isTextContent()` method logic (simplified version)
|
|
52
|
-
- Text files using `updateText()` vs binary files using direct assignment
|
|
53
|
-
- Mixed content operations in single sync
|
|
54
|
-
- Text CRDT conflict resolution behavior
|
|
55
|
-
|
|
56
|
-
### 4. CLI Command Integration (MEDIUM PRIORITY)
|
|
57
|
-
|
|
58
|
-
**Status:** ❌ No end-to-end CLI tests
|
|
59
|
-
**Impact:** User-facing functionality untested
|
|
60
|
-
|
|
61
|
-
**Missing Coverage:**
|
|
62
|
-
|
|
63
|
-
- `init` command with directory document creation
|
|
64
|
-
- `sync` command full bidirectional flow
|
|
65
|
-
- `status`, `diff`, `log` command accuracy
|
|
66
|
-
- Error handling in CLI commands
|
|
67
|
-
- `--dry-run` flag functionality
|
|
68
|
-
|
|
69
|
-
### 5. Automerge Repository Integration (LOW PRIORITY)
|
|
70
|
-
|
|
71
|
-
**Status:** ❌ No Automerge-specific tests
|
|
72
|
-
**Impact:** Repository lifecycle and document management untested
|
|
73
|
-
|
|
74
|
-
**Missing Coverage:**
|
|
75
|
-
|
|
76
|
-
- Document creation and updates
|
|
77
|
-
- Repository shutdown behavior
|
|
78
|
-
- Document head tracking accuracy
|
|
79
|
-
- Network synchronization (mocked)
|
|
80
|
-
|
|
81
|
-
## Technical Challenges
|
|
82
|
-
|
|
83
|
-
### Jest + ES Modules Issue
|
|
84
|
-
|
|
85
|
-
**Problem:** `@automerge/automerge-repo` uses ES modules that Jest struggles to handle
|
|
86
|
-
**Error:** `SyntaxError: Unexpected token 'export'`
|
|
87
|
-
|
|
88
|
-
**Attempted Solutions:**
|
|
89
|
-
|
|
90
|
-
- Modified Jest `transformIgnorePatterns`
|
|
91
|
-
- Added ES module configuration
|
|
92
|
-
- All attempts failed due to complex dependency chain
|
|
93
|
-
|
|
94
|
-
### Recommended Solutions
|
|
95
|
-
|
|
96
|
-
#### Option 1: Integration Testing Approach
|
|
97
|
-
|
|
98
|
-
Create integration tests that test the full flow without mocking Automerge:
|
|
99
|
-
|
|
100
|
-
```typescript
|
|
101
|
-
// test/integration/sync-engine-integration.test.ts
|
|
102
|
-
// Test real sync operations with actual Automerge repositories
|
|
103
|
-
// Focus on end-to-end behavior rather than unit testing
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
#### Option 2: Mock-Heavy Unit Testing
|
|
107
|
-
|
|
108
|
-
Create comprehensive mocks for Automerge dependencies:
|
|
109
|
-
|
|
110
|
-
```typescript
|
|
111
|
-
// Heavy mocking of @automerge/automerge-repo
|
|
112
|
-
// Test SyncEngine logic in isolation
|
|
113
|
-
// More fragile but allows unit testing
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
#### Option 3: Test Configuration Update
|
|
117
|
-
|
|
118
|
-
Update Jest/test environment to properly handle ES modules:
|
|
119
|
-
|
|
120
|
-
- Migrate to newer Jest version with better ES module support
|
|
121
|
-
- Or switch to alternative test runner (Vitest, etc.)
|
|
122
|
-
|
|
123
|
-
## Immediate Actions Taken
|
|
124
|
-
|
|
125
|
-
### Comprehensive `isTextContent()` Testing
|
|
126
|
-
|
|
127
|
-
Since this is a pure function, it can be tested independently:
|
|
128
|
-
|
|
129
|
-
```typescript
|
|
130
|
-
// Manual testing confirmed:
|
|
131
|
-
// ✅ Strings identified as text
|
|
132
|
-
// ✅ Uint8Array identified as binary
|
|
133
|
-
// ✅ Edge cases handled correctly
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### Manual Integration Testing
|
|
137
|
-
|
|
138
|
-
Verified through manual CLI testing:
|
|
139
|
-
|
|
140
|
-
- ✅ Directory document management working
|
|
141
|
-
- ✅ Root directory URL persistence
|
|
142
|
-
- ✅ File creation/deletion updates directory structure
|
|
143
|
-
- ✅ Text files using updateText(), binary files using direct assignment
|
|
144
|
-
|
|
145
|
-
## Recommendations
|
|
146
|
-
|
|
147
|
-
### Short Term (Implemented)
|
|
148
|
-
|
|
149
|
-
1. ✅ Document testing gaps (this file)
|
|
150
|
-
2. ✅ Manual testing of critical functionality
|
|
151
|
-
3. ✅ Existing test suite still passing (89 tests)
|
|
152
|
-
|
|
153
|
-
### Medium Term (Future Work)
|
|
154
|
-
|
|
155
|
-
1. **Integration Test Suite:** Create `test/integration/sync-engine-e2e.test.ts`
|
|
156
|
-
2. **CLI Testing:** Add end-to-end CLI command testing
|
|
157
|
-
3. **Mock Strategy:** Develop proper Automerge mocking approach
|
|
158
|
-
|
|
159
|
-
### Long Term (Future Work)
|
|
160
|
-
|
|
161
|
-
1. **Test Environment Upgrade:** Resolve ES module handling
|
|
162
|
-
2. **Comprehensive Unit Tests:** Full SyncEngine unit test coverage
|
|
163
|
-
3. **Property-Based Testing:** Add property testing for sync operations
|
|
164
|
-
|
|
165
|
-
## Current Test Status
|
|
166
|
-
|
|
167
|
-
- **Total Tests:** 89 passing
|
|
168
|
-
- **Test Suites:** 5 passing
|
|
169
|
-
- **Coverage:** High for utilities, medium for integration, low for core sync logic
|
|
170
|
-
- **Critical Features:** Manually verified but not automatically tested
|
|
171
|
-
|
|
172
|
-
## Conclusion
|
|
173
|
-
|
|
174
|
-
While we've identified significant testing gaps around the new features, the existing test suite provides confidence in the foundation. The new features have been manually tested and are working correctly. Future development should prioritize integration testing to cover the areas where unit testing is blocked by ES module issues.
|
|
@@ -1,236 +0,0 @@
|
|
|
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
|
-
});
|