@x-oasis/map-diff-range 0.1.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/.turbo/turbo-build.log +18 -0
- package/CHANGELOG.md +14 -0
- package/README.md +275 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +8 -0
- package/dist/map-diff-range.cjs.development.js +220 -0
- package/dist/map-diff-range.cjs.development.js.map +1 -0
- package/dist/map-diff-range.cjs.production.min.js +2 -0
- package/dist/map-diff-range.cjs.production.min.js.map +1 -0
- package/dist/map-diff-range.esm.js +213 -0
- package/dist/map-diff-range.esm.js.map +1 -0
- package/examples/.turbo/turbo-build.log +12 -0
- package/examples/README.md +111 -0
- package/examples/index.html +13 -0
- package/examples/package.json +28 -0
- package/examples/src/App.tsx +257 -0
- package/examples/src/index.css +253 -0
- package/examples/src/main.tsx +10 -0
- package/examples/tsconfig.json +25 -0
- package/examples/tsconfig.node.json +10 -0
- package/examples/vite.config.ts +30 -0
- package/package.json +25 -0
- package/src/index.ts +352 -0
- package/test/index.test.ts +234 -0
- package/tsconfig.build.json +11 -0
- package/tsconfig.json +6 -0
- package/vitest.config.ts +21 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { expect, test, describe } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
mapNewerRangeToOlder,
|
|
4
|
+
mapOlderRangeToNewer,
|
|
5
|
+
analyzeFragmentChange,
|
|
6
|
+
resolveGroupChangeFragments,
|
|
7
|
+
} from '../src';
|
|
8
|
+
|
|
9
|
+
describe('mapNewerRangeToOlder', () => {
|
|
10
|
+
test('should map range when content is unchanged', () => {
|
|
11
|
+
const older = 'Hello World';
|
|
12
|
+
const newer = 'Hello World';
|
|
13
|
+
const result = mapNewerRangeToOlder(older, newer, 0, 5);
|
|
14
|
+
expect(result).toEqual({ start: 0, end: 5 });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('should map range when content has insertions', () => {
|
|
18
|
+
const older = 'Hello World';
|
|
19
|
+
const newer = 'Hello Beautiful World';
|
|
20
|
+
// Map range in newer (6-16, "Beautiful ") to older
|
|
21
|
+
const result = mapNewerRangeToOlder(older, newer, 6, 16);
|
|
22
|
+
// Should map to position 6 in older (after "Hello ")
|
|
23
|
+
expect(result.start).toBe(6);
|
|
24
|
+
expect(result.end).toBe(6);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('should map range when content has deletions', () => {
|
|
28
|
+
const older = 'Hello Beautiful World';
|
|
29
|
+
const newer = 'Hello World';
|
|
30
|
+
// Map range in newer (6-6, at deletion point) to older
|
|
31
|
+
const result = mapNewerRangeToOlder(older, newer, 6, 6);
|
|
32
|
+
expect(result.start).toBe(6);
|
|
33
|
+
expect(result.end).toBeGreaterThan(6);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('should map range in equal section', () => {
|
|
37
|
+
const older = 'The quick brown fox';
|
|
38
|
+
const newer = 'The fast brown fox';
|
|
39
|
+
// Map "brown fox" (10-19 in newer) to older
|
|
40
|
+
const result = mapNewerRangeToOlder(older, newer, 10, 19);
|
|
41
|
+
expect(result.start).toBeGreaterThan(0);
|
|
42
|
+
expect(result.end).toBeGreaterThan(result.start);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('should handle range at boundary', () => {
|
|
46
|
+
const older = 'Hello';
|
|
47
|
+
const newer = 'Hello World';
|
|
48
|
+
const result = mapNewerRangeToOlder(older, newer, 5, 11);
|
|
49
|
+
expect(result.start).toBe(5);
|
|
50
|
+
expect(result.end).toBe(5);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('mapOlderRangeToNewer', () => {
|
|
55
|
+
test('should map range when content is unchanged', () => {
|
|
56
|
+
const older = 'Hello World';
|
|
57
|
+
const newer = 'Hello World';
|
|
58
|
+
const result = mapOlderRangeToNewer(older, newer, 0, 5);
|
|
59
|
+
expect(result).toEqual({ start: 0, end: 5 });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('should map range when content has insertions', () => {
|
|
63
|
+
const older = 'Hello World';
|
|
64
|
+
const newer = 'Hello Beautiful World';
|
|
65
|
+
// Map range in older (6-6) to newer
|
|
66
|
+
// Position 6 in older is 'W' (start of "World")
|
|
67
|
+
// In newer, "World" starts at position 16 (after "Hello Beautiful ")
|
|
68
|
+
// Since input is empty range (6-6), output should also be empty range (16-16)
|
|
69
|
+
const result = mapOlderRangeToNewer(older, newer, 6, 10);
|
|
70
|
+
expect(result.start).toBe(16);
|
|
71
|
+
expect(result.end).toBe(20);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('should map range when content has deletions', () => {
|
|
75
|
+
const older = 'Hello Beautiful World';
|
|
76
|
+
const newer = 'Hello World';
|
|
77
|
+
// Map range in older (6-16, "Beautiful ") to newer
|
|
78
|
+
const result = mapOlderRangeToNewer(older, newer, 6, 16);
|
|
79
|
+
expect(result.start).toBe(6);
|
|
80
|
+
expect(result.end).toBe(6);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('should map range in equal section', () => {
|
|
84
|
+
const older = 'The quick brown fox';
|
|
85
|
+
const newer = 'The fast brown fox';
|
|
86
|
+
// Map "brown fox" in older to newer
|
|
87
|
+
const result = mapOlderRangeToNewer(older, newer, 10, 19);
|
|
88
|
+
expect(result.start).toBeGreaterThan(0);
|
|
89
|
+
expect(result.end).toBeGreaterThan(result.start);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('analyzeFragmentChange', () => {
|
|
94
|
+
test('should detect equal fragments', () => {
|
|
95
|
+
const original = 'Hello World';
|
|
96
|
+
const final = 'Hello World';
|
|
97
|
+
const result = analyzeFragmentChange(original, final);
|
|
98
|
+
expect(result.equal).toBe(true);
|
|
99
|
+
expect(result.onlyDeletion).toBe(false);
|
|
100
|
+
expect(result.onlyInsertion).toBe(false);
|
|
101
|
+
expect(result.replacement).toBe(false);
|
|
102
|
+
expect(result.summary).toBe('无变更');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('should detect only deletion', () => {
|
|
106
|
+
const original = 'Hello Beautiful World';
|
|
107
|
+
const final = 'Hello World';
|
|
108
|
+
const result = analyzeFragmentChange(original, final);
|
|
109
|
+
expect(result.equal).toBe(false);
|
|
110
|
+
expect(result.onlyDeletion).toBe(true);
|
|
111
|
+
expect(result.onlyInsertion).toBe(false);
|
|
112
|
+
expect(result.replacement).toBe(false);
|
|
113
|
+
expect(result.summary).toContain('删除:');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('should detect only insertion', () => {
|
|
117
|
+
const original = 'Hello World';
|
|
118
|
+
const final = 'Hello Beautiful World';
|
|
119
|
+
const result = analyzeFragmentChange(original, final);
|
|
120
|
+
expect(result.equal).toBe(false);
|
|
121
|
+
expect(result.onlyDeletion).toBe(false);
|
|
122
|
+
expect(result.onlyInsertion).toBe(true);
|
|
123
|
+
expect(result.replacement).toBe(false);
|
|
124
|
+
expect(result.summary).toContain('新增:');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('should detect replacement', () => {
|
|
128
|
+
const original = 'The quick brown fox';
|
|
129
|
+
const final = 'The fast brown fox';
|
|
130
|
+
const result = analyzeFragmentChange(original, final);
|
|
131
|
+
expect(result.equal).toBe(false);
|
|
132
|
+
expect(result.onlyDeletion).toBe(false);
|
|
133
|
+
expect(result.onlyInsertion).toBe(false);
|
|
134
|
+
expect(result.replacement).toBe(true);
|
|
135
|
+
expect(result.summary).toContain('替换:');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('should include diffs array', () => {
|
|
139
|
+
const original = 'Hello World';
|
|
140
|
+
const final = 'Hello Beautiful World';
|
|
141
|
+
const result = analyzeFragmentChange(original, final);
|
|
142
|
+
expect(Array.isArray(result.diffs)).toBe(true);
|
|
143
|
+
expect(result.diffs.length).toBeGreaterThan(0);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('resolveGroupChangeFragments', () => {
|
|
148
|
+
test('should resolve fragments for simple change', () => {
|
|
149
|
+
const current = 'Hello Beautiful World';
|
|
150
|
+
const next = 'Hello Amazing World';
|
|
151
|
+
const result = resolveGroupChangeFragments({
|
|
152
|
+
currentContent: current,
|
|
153
|
+
nextContent: next,
|
|
154
|
+
currentRange: { start: 6, end: 15 },
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
expect(result).toBeDefined();
|
|
158
|
+
if (result) {
|
|
159
|
+
expect(result.nextRange).toBeDefined();
|
|
160
|
+
expect(result.currentFragment).toBeDefined();
|
|
161
|
+
expect(result.nextFragment).toBeDefined();
|
|
162
|
+
expect(result.changeAnalysis).toBeDefined();
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('should return undefined for invalid offset range', () => {
|
|
167
|
+
const current = 'Hello World';
|
|
168
|
+
const next = 'Hello World';
|
|
169
|
+
const result = resolveGroupChangeFragments({
|
|
170
|
+
currentContent: current,
|
|
171
|
+
nextContent: next,
|
|
172
|
+
currentRange: { start: -1, end: 5 },
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
expect(result).toBeUndefined();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('should return undefined when endOffset exceeds content length', () => {
|
|
179
|
+
const current = 'Hello World';
|
|
180
|
+
const next = 'Hello World';
|
|
181
|
+
const result = resolveGroupChangeFragments({
|
|
182
|
+
currentContent: current,
|
|
183
|
+
nextContent: next,
|
|
184
|
+
currentRange: { start: 0, end: 1000 },
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
expect(result).toBeUndefined();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('should return undefined when startOffset > endOffset', () => {
|
|
191
|
+
const current = 'Hello World';
|
|
192
|
+
const next = 'Hello World';
|
|
193
|
+
const result = resolveGroupChangeFragments({
|
|
194
|
+
currentContent: current,
|
|
195
|
+
nextContent: next,
|
|
196
|
+
currentRange: { start: 10, end: 5 },
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
expect(result).toBeUndefined();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test('should handle complex change', () => {
|
|
203
|
+
const current = '<h1 class="title text-xl">Name</h1>';
|
|
204
|
+
const next = '<h1 class="text-xl font-bold">Name</h1>';
|
|
205
|
+
const result = resolveGroupChangeFragments({
|
|
206
|
+
currentContent: current,
|
|
207
|
+
nextContent: next,
|
|
208
|
+
currentRange: { start: 4, end: 30 },
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
expect(result).toBeDefined();
|
|
212
|
+
if (result) {
|
|
213
|
+
expect(result.changeAnalysis.replacement).toBe(true);
|
|
214
|
+
expect(result.currentFragment).toContain('title');
|
|
215
|
+
expect(result.nextFragment).toContain('font-bold');
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test('should handle empty range', () => {
|
|
220
|
+
const current = 'Hello World';
|
|
221
|
+
const next = 'Hello World';
|
|
222
|
+
const result = resolveGroupChangeFragments({
|
|
223
|
+
currentContent: current,
|
|
224
|
+
nextContent: next,
|
|
225
|
+
currentRange: { start: 5, end: 5 },
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
expect(result).toBeDefined();
|
|
229
|
+
if (result) {
|
|
230
|
+
expect(result.currentFragment).toBe('');
|
|
231
|
+
expect(result.nextFragment).toBe('');
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
});
|
package/tsconfig.json
ADDED
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
globals: true,
|
|
6
|
+
include: ['test/**/*.(spec|test).ts'],
|
|
7
|
+
exclude: ['node_modules/**'],
|
|
8
|
+
threads: false,
|
|
9
|
+
|
|
10
|
+
coverage: {
|
|
11
|
+
provider: 'istanbul',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
resolve: {
|
|
16
|
+
alias: {},
|
|
17
|
+
},
|
|
18
|
+
define: {
|
|
19
|
+
__DEV__: false,
|
|
20
|
+
},
|
|
21
|
+
});
|