@diffson/core 1.0.0 → 1.0.1
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 +264 -0
- package/package.json +5 -1
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-test.log +0 -96
- package/.turbo/turbo-typecheck.log +0 -2
- package/src/contract/constant/DiffConstants.ts +0 -10
- package/src/contract/constant/PresetName.ts +0 -6
- package/src/contract/constant/index.ts +0 -2
- package/src/contract/index.ts +0 -2
- package/src/contract/type/ArrayComparator.ts +0 -15
- package/src/contract/type/ComparatorOrchestrator.ts +0 -16
- package/src/contract/type/DiffService.ts +0 -31
- package/src/contract/type/JsonTypes.ts +0 -3
- package/src/contract/type/NullComparator.ts +0 -9
- package/src/contract/type/ObjectComparator.ts +0 -15
- package/src/contract/type/OtherComparator.ts +0 -14
- package/src/contract/type/PrimitiveComparator.ts +0 -13
- package/src/contract/type/Result.ts +0 -7
- package/src/contract/type/SingleNodeDifference.ts +0 -13
- package/src/contract/type/index.ts +0 -10
- package/src/index.ts +0 -17
- package/src/service/comparator/ComparatorOrchestrator.ts +0 -50
- package/src/service/comparator/array/AbstractArray.ts +0 -34
- package/src/service/comparator/array/SequentialArrayComparator.test.ts +0 -46
- package/src/service/comparator/array/SequentialArrayComparator.ts +0 -48
- package/src/service/comparator/array/SimilarArrayComparator.test.ts +0 -37
- package/src/service/comparator/array/SimilarArrayComparator.ts +0 -211
- package/src/service/comparator/array/index.ts +0 -3
- package/src/service/comparator/index.ts +0 -6
- package/src/service/comparator/nulls/DefaultNullComparator.test.ts +0 -38
- package/src/service/comparator/nulls/DefaultNullComparator.ts +0 -9
- package/src/service/comparator/nulls/index.ts +0 -1
- package/src/service/comparator/object/AbstractObject.ts +0 -102
- package/src/service/comparator/object/LeftJoinObjectComparator.test.ts +0 -71
- package/src/service/comparator/object/LeftJoinObjectComparator.ts +0 -18
- package/src/service/comparator/object/UnionKeyObjectComparator.test.ts +0 -20
- package/src/service/comparator/object/UnionKeyObjectComparator.ts +0 -19
- package/src/service/comparator/object/index.ts +0 -3
- package/src/service/comparator/other/DefaultOtherComparator.test.ts +0 -48
- package/src/service/comparator/other/DefaultOtherComparator.ts +0 -23
- package/src/service/comparator/other/index.ts +0 -1
- package/src/service/comparator/primitive/DefaultPrimitiveComparator.test.ts +0 -50
- package/src/service/comparator/primitive/DefaultPrimitiveComparator.ts +0 -27
- package/src/service/comparator/primitive/index.ts +0 -1
- package/src/service/diff/Diff.test.ts +0 -113
- package/src/service/diff/DiffContext.ts +0 -37
- package/src/service/diff/DiffService.test.ts +0 -292
- package/src/service/diff/DiffService.ts +0 -245
- package/src/service/diff/PathTracker.ts +0 -71
- package/src/service/diff/index.ts +0 -1
- package/src/service/index.ts +0 -2
- package/src/util/TypeGuards.test.ts +0 -90
- package/src/util/TypeGuards.ts +0 -33
- package/src/util/index.ts +0 -1
- package/tsconfig.json +0 -11
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "bun:test";
|
|
2
|
-
import { DiffService } from "../../diff/DiffService";
|
|
3
|
-
|
|
4
|
-
describe("DefaultOtherComparator", () => {
|
|
5
|
-
it("should detect type changes from object to array", () => {
|
|
6
|
-
const left = { value: { a: 1 } };
|
|
7
|
-
const right = { value: [1, 2, 3] };
|
|
8
|
-
|
|
9
|
-
const diffService = new DiffService();
|
|
10
|
-
const results = diffService.diffElement(left, right);
|
|
11
|
-
|
|
12
|
-
expect(results.length).toBe(1);
|
|
13
|
-
expect(results[0].diffType).toBe("MODIFY");
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it("should detect type changes from array to primitive", () => {
|
|
17
|
-
const left = { value: [1, 2, 3] };
|
|
18
|
-
const right = { value: 123 };
|
|
19
|
-
|
|
20
|
-
const diffService = new DiffService();
|
|
21
|
-
const results = diffService.diffElement(left, right);
|
|
22
|
-
|
|
23
|
-
expect(results.length).toBe(1);
|
|
24
|
-
expect(results[0].diffType).toBe("MODIFY");
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("should detect undefined to value", () => {
|
|
28
|
-
const left = {};
|
|
29
|
-
const right = { value: 1 };
|
|
30
|
-
|
|
31
|
-
const diffService = new DiffService();
|
|
32
|
-
const results = diffService.diffElement(left, right);
|
|
33
|
-
|
|
34
|
-
expect(results.length).toBe(1);
|
|
35
|
-
expect(results[0].diffType).toBe("ADD");
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it("should detect value to undefined", () => {
|
|
39
|
-
const left = { value: 1 };
|
|
40
|
-
const right = {};
|
|
41
|
-
|
|
42
|
-
const diffService = new DiffService();
|
|
43
|
-
const results = diffService.diffElement(left, right);
|
|
44
|
-
|
|
45
|
-
expect(results.length).toBe(1);
|
|
46
|
-
expect(results[0].diffType).toBe("DELETE");
|
|
47
|
-
});
|
|
48
|
-
});
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { IOtherComparator, JsonValue } from "../../../contract/type";
|
|
2
|
-
import { DiffContext } from "../../diff/DiffContext";
|
|
3
|
-
import type { PathTracker } from "../../diff/PathTracker";
|
|
4
|
-
import { SingleNodeDifference } from "../../../contract/type";
|
|
5
|
-
import { DIFFERENT, MERGE_PATH } from "../../../contract/constant";
|
|
6
|
-
import { jsonElement2Str } from "../../../util";
|
|
7
|
-
|
|
8
|
-
export class DefaultOtherComparator implements IOtherComparator {
|
|
9
|
-
diff(a: JsonValue | undefined, b: JsonValue | undefined, pathTracker: PathTracker): DiffContext {
|
|
10
|
-
const otherDiffContext = new DiffContext(DIFFERENT);
|
|
11
|
-
const singleNodeDifferences: SingleNodeDifference[] = [];
|
|
12
|
-
singleNodeDifferences.push(
|
|
13
|
-
new SingleNodeDifference(
|
|
14
|
-
pathTracker.getLeftPath().join(MERGE_PATH),
|
|
15
|
-
pathTracker.getRightPath().join(MERGE_PATH),
|
|
16
|
-
jsonElement2Str(a),
|
|
17
|
-
jsonElement2Str(b)
|
|
18
|
-
)
|
|
19
|
-
);
|
|
20
|
-
otherDiffContext.setDiffResultModels(singleNodeDifferences);
|
|
21
|
-
return otherDiffContext;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./DefaultOtherComparator";
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "bun:test";
|
|
2
|
-
import { DiffService } from "../../diff/DiffService";
|
|
3
|
-
|
|
4
|
-
describe("DefaultPrimitiveComparator", () => {
|
|
5
|
-
it("should detect string changes", () => {
|
|
6
|
-
const left = { value: "hello" };
|
|
7
|
-
const right = { value: "world" };
|
|
8
|
-
|
|
9
|
-
const diffService = new DiffService();
|
|
10
|
-
const results = diffService.diffElement(left, right);
|
|
11
|
-
|
|
12
|
-
expect(results.length).toBe(1);
|
|
13
|
-
expect(results[0].left).toBe("hello");
|
|
14
|
-
expect(results[0].right).toBe("world");
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it("should detect number changes", () => {
|
|
18
|
-
const left = { value: 1 };
|
|
19
|
-
const right = { value: 2 };
|
|
20
|
-
|
|
21
|
-
const diffService = new DiffService();
|
|
22
|
-
const results = diffService.diffElement(left, right);
|
|
23
|
-
|
|
24
|
-
expect(results.length).toBe(1);
|
|
25
|
-
expect(results[0].left).toBe("1");
|
|
26
|
-
expect(results[0].right).toBe("2");
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("should detect boolean changes", () => {
|
|
30
|
-
const left = { value: true };
|
|
31
|
-
const right = { value: false };
|
|
32
|
-
|
|
33
|
-
const diffService = new DiffService();
|
|
34
|
-
const results = diffService.diffElement(left, right);
|
|
35
|
-
|
|
36
|
-
expect(results.length).toBe(1);
|
|
37
|
-
expect(results[0].left).toBe("true");
|
|
38
|
-
expect(results[0].right).toBe("false");
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("should not report identical primitives", () => {
|
|
42
|
-
const left = { value: "same" };
|
|
43
|
-
const right = { value: "same" };
|
|
44
|
-
|
|
45
|
-
const diffService = new DiffService();
|
|
46
|
-
const results = diffService.diffElement(left, right);
|
|
47
|
-
|
|
48
|
-
expect(results).toEqual([]);
|
|
49
|
-
});
|
|
50
|
-
});
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { IPrimitiveComparator } from "../../../contract/type";
|
|
2
|
-
import { DiffContext } from "../../diff/DiffContext";
|
|
3
|
-
import type { PathTracker } from "../../diff/PathTracker";
|
|
4
|
-
import { SingleNodeDifference } from "../../../contract/type";
|
|
5
|
-
import { DIFFERENT, MERGE_PATH } from "../../../contract/constant";
|
|
6
|
-
import { jsonElement2Str } from "../../../util";
|
|
7
|
-
|
|
8
|
-
export class DefaultPrimitiveComparator implements IPrimitiveComparator {
|
|
9
|
-
diff(a: string | number | boolean, b: string | number | boolean, pathTracker: PathTracker): DiffContext {
|
|
10
|
-
const primitiveDiffContext = new DiffContext();
|
|
11
|
-
|
|
12
|
-
if (a !== b) {
|
|
13
|
-
const singleNodeDifferences: SingleNodeDifference[] = [];
|
|
14
|
-
singleNodeDifferences.push(
|
|
15
|
-
new SingleNodeDifference(
|
|
16
|
-
pathTracker.getLeftPath().join(MERGE_PATH),
|
|
17
|
-
pathTracker.getRightPath().join(MERGE_PATH),
|
|
18
|
-
jsonElement2Str(a),
|
|
19
|
-
jsonElement2Str(b)
|
|
20
|
-
)
|
|
21
|
-
);
|
|
22
|
-
primitiveDiffContext.setDiffResultModels(singleNodeDifferences);
|
|
23
|
-
primitiveDiffContext.setSame(DIFFERENT);
|
|
24
|
-
}
|
|
25
|
-
return primitiveDiffContext;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./DefaultPrimitiveComparator";
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "bun:test";
|
|
2
|
-
import { DiffService } from "./DiffService";
|
|
3
|
-
|
|
4
|
-
describe("DiffService", () => {
|
|
5
|
-
describe("diffElement", () => {
|
|
6
|
-
it("should return empty array for identical objects", () => {
|
|
7
|
-
const left = { name: "test", value: 1 };
|
|
8
|
-
const right = { name: "test", value: 1 };
|
|
9
|
-
|
|
10
|
-
const diffService = new DiffService();
|
|
11
|
-
const results = diffService.diffElement(left, right);
|
|
12
|
-
|
|
13
|
-
expect(results).toEqual([]);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it("should detect modified values", () => {
|
|
17
|
-
const left = { name: "test", value: 1 };
|
|
18
|
-
const right = { name: "test", value: 2 };
|
|
19
|
-
|
|
20
|
-
const diffService = new DiffService();
|
|
21
|
-
const results = diffService.diffElement(left, right);
|
|
22
|
-
|
|
23
|
-
expect(results.length).toBe(1);
|
|
24
|
-
expect(results[0].diffType).toBe("MODIFY");
|
|
25
|
-
expect(results[0].leftPath).toBe("value");
|
|
26
|
-
expect(results[0].left).toBe("1");
|
|
27
|
-
expect(results[0].right).toBe("2");
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("should detect added fields", () => {
|
|
31
|
-
const left = { name: "test" };
|
|
32
|
-
const right = { name: "test", value: 1 };
|
|
33
|
-
|
|
34
|
-
const diffService = new DiffService();
|
|
35
|
-
const results = diffService.diffElement(left, right);
|
|
36
|
-
|
|
37
|
-
expect(results.length).toBe(1);
|
|
38
|
-
expect(results[0].diffType).toBe("ADD");
|
|
39
|
-
expect(results[0].rightPath).toBe("value");
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it("should detect deleted fields", () => {
|
|
43
|
-
const left = { name: "test", value: 1 };
|
|
44
|
-
const right = { name: "test" };
|
|
45
|
-
|
|
46
|
-
const diffService = new DiffService();
|
|
47
|
-
const results = diffService.diffElement(left, right);
|
|
48
|
-
|
|
49
|
-
expect(results.length).toBe(1);
|
|
50
|
-
expect(results[0].diffType).toBe("DELETE");
|
|
51
|
-
expect(results[0].leftPath).toBe("value");
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
describe("diffJson", () => {
|
|
56
|
-
it("should parse JSON strings and diff them", () => {
|
|
57
|
-
const leftJson = '{"name":"test","value":1}';
|
|
58
|
-
const rightJson = '{"name":"test","value":2}';
|
|
59
|
-
|
|
60
|
-
const diffService = new DiffService();
|
|
61
|
-
const results = diffService.diffJson(leftJson, rightJson);
|
|
62
|
-
|
|
63
|
-
expect(results.length).toBe(1);
|
|
64
|
-
expect(results[0].diffType).toBe("MODIFY");
|
|
65
|
-
expect(results[0].leftPath).toBe("value");
|
|
66
|
-
expect(results[0].left).toBe("1");
|
|
67
|
-
expect(results[0].right).toBe("2");
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it("should return empty array for identical JSON strings", () => {
|
|
71
|
-
const leftJson = '{"name":"test","value":1}';
|
|
72
|
-
const rightJson = '{"name":"test","value":1}';
|
|
73
|
-
|
|
74
|
-
const diffService = new DiffService();
|
|
75
|
-
const results = diffService.diffJson(leftJson, rightJson);
|
|
76
|
-
|
|
77
|
-
expect(results).toEqual([]);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("should throw error for invalid left JSON string", () => {
|
|
81
|
-
const leftJson = '{invalid json}';
|
|
82
|
-
const rightJson = '{"name":"test"}';
|
|
83
|
-
|
|
84
|
-
const diffService = new DiffService();
|
|
85
|
-
|
|
86
|
-
expect(() => diffService.diffJson(leftJson, rightJson)).toThrow("Failed to parse left JSON string");
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("should throw error for invalid right JSON string", () => {
|
|
90
|
-
const leftJson = '{"name":"test"}';
|
|
91
|
-
const rightJson = '{invalid json}';
|
|
92
|
-
|
|
93
|
-
const diffService = new DiffService();
|
|
94
|
-
|
|
95
|
-
expect(() => diffService.diffJson(leftJson, rightJson)).toThrow("Failed to parse right JSON string");
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
describe("nested objects", () => {
|
|
100
|
-
it("should handle nested objects", () => {
|
|
101
|
-
const left = { user: { name: "Alice", age: 30 } };
|
|
102
|
-
const right = { user: { name: "Bob", age: 30 } };
|
|
103
|
-
|
|
104
|
-
const diffService = new DiffService();
|
|
105
|
-
const results = diffService.diffElement(left, right);
|
|
106
|
-
|
|
107
|
-
expect(results.length).toBe(1);
|
|
108
|
-
expect(results[0].leftPath).toBe("user.name");
|
|
109
|
-
expect(results[0].left).toBe("Alice");
|
|
110
|
-
expect(results[0].right).toBe("Bob");
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
});
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { SingleNodeDifference } from "../../contract/type";
|
|
2
|
-
|
|
3
|
-
export class DiffContext {
|
|
4
|
-
private _isSame: boolean;
|
|
5
|
-
private singleNodeDifferences: SingleNodeDifference[];
|
|
6
|
-
private specialPathResult: string[] | null;
|
|
7
|
-
|
|
8
|
-
constructor(isSame: boolean = true) {
|
|
9
|
-
this._isSame = isSame;
|
|
10
|
-
this.singleNodeDifferences = [];
|
|
11
|
-
this.specialPathResult = null;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
isSame(): boolean {
|
|
15
|
-
return this._isSame;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
setSame(same: boolean): void {
|
|
19
|
-
this._isSame = same;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
getDiffResultModels(): SingleNodeDifference[] {
|
|
23
|
-
return this.singleNodeDifferences;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
setDiffResultModels(singleNodeDifferences: SingleNodeDifference[]): void {
|
|
27
|
-
this.singleNodeDifferences = singleNodeDifferences;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
getSpecialPathResult(): string[] | null {
|
|
31
|
-
return this.specialPathResult;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
setSpecialPathResult(specialPathResult: string[]): void {
|
|
35
|
-
this.specialPathResult = specialPathResult;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "bun:test";
|
|
2
|
-
import { DiffService } from "./DiffService";
|
|
3
|
-
import { LeftJoinObjectComparator, SequentialArrayComparator } from "#service";
|
|
4
|
-
import { PresetName } from "#contract";
|
|
5
|
-
|
|
6
|
-
describe("DiffService", () => {
|
|
7
|
-
describe("basic comparison", () => {
|
|
8
|
-
it("should return empty array for identical objects", () => {
|
|
9
|
-
const left = { name: "test", value: 1 };
|
|
10
|
-
const right = { name: "test", value: 1 };
|
|
11
|
-
|
|
12
|
-
const diffService = new DiffService();
|
|
13
|
-
const results = diffService.diffElement(left, right);
|
|
14
|
-
|
|
15
|
-
expect(results).toEqual([]);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it("should detect modified values", () => {
|
|
19
|
-
const left = { name: "test", value: 1 };
|
|
20
|
-
const right = { name: "test", value: 2 };
|
|
21
|
-
|
|
22
|
-
const diffService = new DiffService();
|
|
23
|
-
const results = diffService.diffElement(left, right);
|
|
24
|
-
|
|
25
|
-
expect(results.length).toBe(1);
|
|
26
|
-
expect(results[0].diffType).toBe("MODIFY");
|
|
27
|
-
expect(results[0].leftPath).toBe("value");
|
|
28
|
-
expect(results[0].left).toBe("1");
|
|
29
|
-
expect(results[0].right).toBe("2");
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
describe("withArrayComparator", () => {
|
|
34
|
-
it("should use sequential array comparator when configured", () => {
|
|
35
|
-
const left = { items: [1, 2, 3] };
|
|
36
|
-
const right = { items: [1, 4, 3] };
|
|
37
|
-
|
|
38
|
-
const diffService = new DiffService().withArrayComparator(SequentialArrayComparator);
|
|
39
|
-
const results = diffService.diffElement(left, right);
|
|
40
|
-
console.log(results);
|
|
41
|
-
|
|
42
|
-
expect(results.length).toBe(1);
|
|
43
|
-
expect(results[0].leftPath).toBe("items.[1]");
|
|
44
|
-
expect(results[0].left).toBe("2");
|
|
45
|
-
expect(results[0].right).toBe("4");
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("should reuse injector when configuration changes", () => {
|
|
49
|
-
const diffService = new DiffService();
|
|
50
|
-
|
|
51
|
-
// First comparison with default comparator
|
|
52
|
-
const left1 = { items: [1, 2] };
|
|
53
|
-
const right1 = { items: [2, 1] };
|
|
54
|
-
const results1 = diffService.diffElement(left1, right1);
|
|
55
|
-
|
|
56
|
-
// Should match similar elements (default behavior)
|
|
57
|
-
expect(results1).toEqual([]);
|
|
58
|
-
|
|
59
|
-
// Change to sequential comparator
|
|
60
|
-
diffService.withArrayComparator(SequentialArrayComparator);
|
|
61
|
-
|
|
62
|
-
// Second comparison with sequential comparator
|
|
63
|
-
const results2 = diffService.diffElement(left1, right1);
|
|
64
|
-
|
|
65
|
-
// Should detect differences at each index
|
|
66
|
-
expect(results2.length).toBe(2);
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
describe("preset", () => {
|
|
71
|
-
it("should use FullOrdered preset", () => {
|
|
72
|
-
const left = { items: [1, 2, 3] };
|
|
73
|
-
const right = { items: [1, 4, 3] };
|
|
74
|
-
|
|
75
|
-
const diffService = new DiffService(PresetName.FullOrdered);
|
|
76
|
-
const results = diffService.diffElement(left, right);
|
|
77
|
-
|
|
78
|
-
expect(results.length).toBe(1);
|
|
79
|
-
expect(results[0].leftPath).toBe("items.[1]");
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("should use LeftSmart preset", () => {
|
|
83
|
-
const left = { a: 1, b: 2 };
|
|
84
|
-
const right = { a: 1, b: 2, c: 3 };
|
|
85
|
-
|
|
86
|
-
const diffService = new DiffService(PresetName.LeftSmart);
|
|
87
|
-
const results = diffService.diffElement(left, right);
|
|
88
|
-
|
|
89
|
-
// LeftSmart only compares keys from left object
|
|
90
|
-
expect(results).toEqual([]);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it("should use LeftOrdered preset", () => {
|
|
94
|
-
const left = { a: 1, items: [1, 2] };
|
|
95
|
-
const right = { a: 1, b: 2, items: [2, 1] };
|
|
96
|
-
|
|
97
|
-
const diffService = new DiffService(PresetName.LeftOrdered);
|
|
98
|
-
const results = diffService.diffElement(left, right);
|
|
99
|
-
|
|
100
|
-
// Should use leftJoin for objects and sequential for arrays
|
|
101
|
-
expect(results.length).toBe(2); // Two array differences
|
|
102
|
-
expect(results[0].leftPath).toBe("items.[0]");
|
|
103
|
-
expect(results[1].leftPath).toBe("items.[1]");
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
describe("compareWithOptions", () => {
|
|
108
|
-
it("should ignore noise paths", () => {
|
|
109
|
-
const left = { name: "test", timestamp: 1000 };
|
|
110
|
-
const right = { name: "test", timestamp: 2000 };
|
|
111
|
-
|
|
112
|
-
const diffService = new DiffService();
|
|
113
|
-
const results = diffService.diffElement(left, right, {
|
|
114
|
-
noisePath: ["timestamp"],
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
expect(results).toEqual([]);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it("should handle special paths", () => {
|
|
121
|
-
const left = { name: "test", special: "value1" };
|
|
122
|
-
const right = { name: "test", special: "value2" };
|
|
123
|
-
|
|
124
|
-
const diffService = new DiffService();
|
|
125
|
-
const results = diffService.diffElement(left, right, {
|
|
126
|
-
specialPath: ["special"],
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
// Special paths are tracked differently
|
|
130
|
-
expect(results.length).toBe(1);
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
describe("fluent API", () => {
|
|
135
|
-
it("should support method chaining", () => {
|
|
136
|
-
const diffService = new DiffService()
|
|
137
|
-
.withArrayComparator(SequentialArrayComparator)
|
|
138
|
-
.withObjectComparator(LeftJoinObjectComparator);
|
|
139
|
-
|
|
140
|
-
const left = { a: 1, items: [1, 2] };
|
|
141
|
-
const right = { a: 1, b: 2, items: [2, 1] };
|
|
142
|
-
|
|
143
|
-
const results = diffService.diffElement(left, right);
|
|
144
|
-
|
|
145
|
-
// Should use both custom comparators
|
|
146
|
-
expect(results.length).toBeGreaterThan(0);
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
describe("DiffService - parseNestedJson", () => {
|
|
152
|
-
it("should parse nested JSON strings when parseNestedJson option is true", () => {
|
|
153
|
-
const leftJson = JSON.stringify({
|
|
154
|
-
user: '{"name": "Alice", "age": 30}',
|
|
155
|
-
settings: '{"theme": "dark", "notifications": true}'
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
const rightJson = JSON.stringify({
|
|
159
|
-
user: '{"name": "Bob", "age": 25}',
|
|
160
|
-
settings: '{"theme": "light", "notifications": false}'
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
const diffService = new DiffService();
|
|
164
|
-
const results = diffService.diffJson(leftJson, rightJson, {
|
|
165
|
-
parseNestedJson: true
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
// 应该能检测到嵌套对象内部的差异
|
|
169
|
-
expect(results.length).toBeGreaterThan(0);
|
|
170
|
-
|
|
171
|
-
// 验证路径能正确指向嵌套对象的属性
|
|
172
|
-
const nameDiff = results.find(r => r.leftPath === "user.name");
|
|
173
|
-
expect(nameDiff).toBeDefined();
|
|
174
|
-
expect(nameDiff?.left).toBe("Alice");
|
|
175
|
-
expect(nameDiff?.right).toBe("Bob");
|
|
176
|
-
|
|
177
|
-
const themeDiff = results.find(r => r.leftPath === "settings.theme");
|
|
178
|
-
expect(themeDiff).toBeDefined();
|
|
179
|
-
expect(themeDiff?.left).toBe("dark");
|
|
180
|
-
expect(themeDiff?.right).toBe("light");
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it("should not parse nested JSON strings when parseNestedJson option is false or undefined", () => {
|
|
184
|
-
const leftJson = JSON.stringify({
|
|
185
|
-
user: '{"name": "Alice", "age": 30}'
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
const rightJson = JSON.stringify({
|
|
189
|
-
user: '{"name": "Bob", "age": 25}'
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
const diffService = new DiffService();
|
|
193
|
-
const results = diffService.diffJson(leftJson, rightJson);
|
|
194
|
-
|
|
195
|
-
// 不应该解析嵌套 JSON,应该把整个字符串当作一个值
|
|
196
|
-
expect(results.length).toBe(1);
|
|
197
|
-
expect(results[0].leftPath).toBe("user");
|
|
198
|
-
expect(results[0].left).toBe('{"name": "Alice", "age": 30}');
|
|
199
|
-
expect(results[0].right).toBe('{"name": "Bob", "age": 25}');
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it("should handle deeply nested JSON strings", () => {
|
|
203
|
-
const leftJson = JSON.stringify({
|
|
204
|
-
data: '{"level1": {"level2": "value1"}}'
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
const rightJson = JSON.stringify({
|
|
208
|
-
data: '{"level1": {"level2": "value2"}}'
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
const diffService = new DiffService();
|
|
212
|
-
const results = diffService.diffJson(leftJson, rightJson, {
|
|
213
|
-
parseNestedJson: true
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
// 应该能检测到多层嵌套的差异
|
|
217
|
-
const deepDiff = results.find(r => r.leftPath === "data.level1.level2");
|
|
218
|
-
expect(deepDiff).toBeDefined();
|
|
219
|
-
expect(deepDiff?.left).toBe("value1");
|
|
220
|
-
expect(deepDiff?.right).toBe("value2");
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it("should handle arrays containing JSON strings", () => {
|
|
224
|
-
const leftJson = JSON.stringify({
|
|
225
|
-
items: ['{"id": 1, "name": "Item1"}', '{"id": 2, "name": "Item2"}']
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
const rightJson = JSON.stringify({
|
|
229
|
-
items: ['{"id": 1, "name": "Item1"}', '{"id": 2, "name": "Item2Modified"}']
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
const diffService = new DiffService();
|
|
233
|
-
const results = diffService.diffJson(leftJson, rightJson, {
|
|
234
|
-
parseNestedJson: true
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
// 应该能检测到数组元素中嵌套对象的差异
|
|
238
|
-
const itemDiff = results.find(r => r.leftPath === "items.[1].name");
|
|
239
|
-
expect(itemDiff).toBeDefined();
|
|
240
|
-
expect(itemDiff?.left).toBe("Item2");
|
|
241
|
-
expect(itemDiff?.right).toBe("Item2Modified");
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
it("should handle mixed content with both JSON strings and regular strings", () => {
|
|
245
|
-
const leftJson = JSON.stringify({
|
|
246
|
-
regularString: "Hello",
|
|
247
|
-
jsonString: '{"key": "value1"}',
|
|
248
|
-
number: 42
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
const rightJson = JSON.stringify({
|
|
252
|
-
regularString: "World",
|
|
253
|
-
jsonString: '{"key": "value2"}',
|
|
254
|
-
number: 42
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
const diffService = new DiffService(PresetName.LeftOrdered);
|
|
258
|
-
const results = diffService.diffJson(leftJson, rightJson, {
|
|
259
|
-
parseNestedJson: true
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
expect(results.length).toBe(2);
|
|
263
|
-
|
|
264
|
-
const regularDiff = results.find(r => r.leftPath === "regularString");
|
|
265
|
-
expect(regularDiff?.left).toBe("Hello");
|
|
266
|
-
expect(regularDiff?.right).toBe("World");
|
|
267
|
-
|
|
268
|
-
const jsonDiff = results.find(r => r.leftPath === "jsonString.key");
|
|
269
|
-
expect(jsonDiff?.left).toBe("value1");
|
|
270
|
-
expect(jsonDiff?.right).toBe("value2");
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
it("should not fail on invalid JSON strings", () => {
|
|
274
|
-
const leftJson = JSON.stringify({
|
|
275
|
-
invalid: '{"not valid json'
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
const rightJson = JSON.stringify({
|
|
279
|
-
invalid: '{"not valid json'
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
const diffService = new DiffService();
|
|
283
|
-
const results = diffService.diffJson(leftJson, rightJson, {
|
|
284
|
-
parseNestedJson: true
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
// 无效的 JSON 字符串应该保持原样
|
|
288
|
-
expect(results.length).toBe(0);
|
|
289
|
-
});
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
|