244 lines
8.6 KiB
JavaScript
244 lines
8.6 KiB
JavaScript
import { describe, it, expect } from 'vitest'
|
|
import {
|
|
NodeComparisonResult,
|
|
compareJsonNodes,
|
|
compareJson,
|
|
stringSimilarity,
|
|
calculateStats,
|
|
formatJsonData,
|
|
} from '../../../src/utils/comparator/jsonCompare.js'
|
|
|
|
describe('comparator/jsonCompare', () => {
|
|
describe('stringSimilarity', () => {
|
|
it('returns same for identical strings', () => {
|
|
expect(stringSimilarity('hello', 'hello')).toEqual({ type: 'same', similarity: 1.0 })
|
|
})
|
|
|
|
it('returns same for two empty strings', () => {
|
|
expect(stringSimilarity('', '')).toEqual({ type: 'same', similarity: 1.0 })
|
|
})
|
|
|
|
it('returns different for completely unrelated strings', () => {
|
|
expect(stringSimilarity('abc', 'xyz')).toEqual({ type: 'different', similarity: 0.0 })
|
|
})
|
|
|
|
it('returns similar for partially matching strings', () => {
|
|
const result = stringSimilarity('hello', 'hallo')
|
|
expect(result.type).toBe('similar')
|
|
expect(result.similarity).toBeGreaterThan(0)
|
|
expect(result.similarity).toBeLessThan(1)
|
|
})
|
|
})
|
|
|
|
describe('compareJsonNodes', () => {
|
|
it('compares identical primitives', () => {
|
|
expect(compareJsonNodes(1, 1).type).toBe(NodeComparisonResult.SAME)
|
|
expect(compareJsonNodes(true, true).type).toBe(NodeComparisonResult.SAME)
|
|
expect(compareJsonNodes(false, false).type).toBe(NodeComparisonResult.SAME)
|
|
})
|
|
|
|
it('compares different numbers as DIFFERENT', () => {
|
|
expect(compareJsonNodes(1, 2).type).toBe(NodeComparisonResult.DIFFERENT)
|
|
})
|
|
|
|
it('treats null === null as SAME', () => {
|
|
expect(compareJsonNodes(null, null).type).toBe(NodeComparisonResult.SAME)
|
|
})
|
|
|
|
it('treats null vs undefined as DIFFERENT', () => {
|
|
expect(compareJsonNodes(null, undefined).type).toBe(NodeComparisonResult.DIFFERENT)
|
|
})
|
|
|
|
it('treats null vs value as DIFFERENT', () => {
|
|
expect(compareJsonNodes(null, 1).type).toBe(NodeComparisonResult.DIFFERENT)
|
|
expect(compareJsonNodes(1, null).type).toBe(NodeComparisonResult.DIFFERENT)
|
|
})
|
|
|
|
it('compares strings with similarity', () => {
|
|
const result = compareJsonNodes('hello', 'hallo')
|
|
expect(result.type).toBe(NodeComparisonResult.SIMILAR)
|
|
})
|
|
|
|
it('compares different types as DIFFERENT', () => {
|
|
expect(compareJsonNodes(1, '1').type).toBe(NodeComparisonResult.DIFFERENT)
|
|
expect(compareJsonNodes([], {}).type).toBe(NodeComparisonResult.DIFFERENT)
|
|
})
|
|
|
|
it('compares empty objects as SAME', () => {
|
|
expect(compareJsonNodes({}, {}).type).toBe(NodeComparisonResult.SAME)
|
|
})
|
|
|
|
it('compares empty arrays as SAME', () => {
|
|
expect(compareJsonNodes([], []).type).toBe(NodeComparisonResult.SAME)
|
|
})
|
|
|
|
it('detects added key as SIMILAR map', () => {
|
|
const result = compareJsonNodes({ a: 1 }, { a: 1, b: 2 })
|
|
expect(result.type).toBe(NodeComparisonResult.SIMILAR)
|
|
expect(result.children.some(c => c.key === 'b')).toBe(true)
|
|
})
|
|
|
|
it('detects removed key', () => {
|
|
const result = compareJsonNodes({ a: 1, b: 2 }, { a: 1 })
|
|
expect(result.type).toBe(NodeComparisonResult.SIMILAR)
|
|
})
|
|
|
|
it('detects changed value for same key as SIMILAR', () => {
|
|
const result = compareJsonNodes({ name: 'Alice' }, { name: 'Bob' })
|
|
expect(result.type).toBe(NodeComparisonResult.SIMILAR)
|
|
const nameComp = result.children.find(c => c.key === 'name')
|
|
expect(nameComp.type).toBe(NodeComparisonResult.SIMILAR)
|
|
})
|
|
|
|
it('compares nested objects', () => {
|
|
const a = { user: { name: 'Alice', age: 30 } }
|
|
const b = { user: { name: 'Alice', age: 31 } }
|
|
const result = compareJsonNodes(a, b)
|
|
expect(result.type).toBe(NodeComparisonResult.SIMILAR)
|
|
})
|
|
})
|
|
|
|
describe('compareJsonNodes list order', () => {
|
|
it('preserves order by default (compareLists)', () => {
|
|
const result = compareJsonNodes([1, 2, 3], [1, 3, 2], false)
|
|
expect(result.type).not.toBe(NodeComparisonResult.SAME)
|
|
})
|
|
|
|
it('ignores order when ignoreOrder=true', () => {
|
|
const result = compareJsonNodes([1, 2, 3], [3, 2, 1], true)
|
|
expect(result.type).toBe(NodeComparisonResult.SIMILAR)
|
|
expect(result.similarity).toBeGreaterThan(0)
|
|
expect(result.matches.filter(m => m.indexA !== undefined && m.indexB !== undefined)).toHaveLength(3)
|
|
})
|
|
|
|
it('matches reorderable arrays with same elements', () => {
|
|
const result = compareJsonNodes(
|
|
[{ id: 1 }, { id: 2 }],
|
|
[{ id: 2 }, { id: 1 }],
|
|
true
|
|
)
|
|
expect(result.type).toBe(NodeComparisonResult.SIMILAR)
|
|
expect(result.matches.filter(m => m.indexA !== undefined && m.indexB !== undefined)).toHaveLength(2)
|
|
})
|
|
|
|
it('detects element addition in ordered list', () => {
|
|
const result = compareJsonNodes([1, 2], [1, 2, 3], false)
|
|
expect(result.type).toBe(NodeComparisonResult.SIMILAR)
|
|
})
|
|
|
|
it('handles empty vs non-empty array', () => {
|
|
const result = compareJsonNodes([], [1])
|
|
expect(result.type).toBe(NodeComparisonResult.SIMILAR)
|
|
})
|
|
})
|
|
|
|
describe('calculateStats', () => {
|
|
it('counts same leaf nodes', () => {
|
|
const comp = compareJsonNodes({ a: 1 }, { a: 1 })
|
|
const stats = calculateStats(comp)
|
|
expect(stats.same).toBeGreaterThan(0)
|
|
})
|
|
|
|
it('counts modify for changed string values', () => {
|
|
const comp = compareJsonNodes({ name: 'Alice' }, { name: 'Bob' })
|
|
const stats = calculateStats(comp)
|
|
expect(stats.modify).toBeGreaterThan(0)
|
|
})
|
|
|
|
it('counts insert/delete for added/removed keys', () => {
|
|
const comp = compareJsonNodes({ a: 1 }, { a: 1, b: 2 })
|
|
const stats = calculateStats(comp)
|
|
expect(stats.insert).toBeGreaterThan(0)
|
|
})
|
|
})
|
|
|
|
describe('formatJsonData', () => {
|
|
it('formats null', () => {
|
|
expect(formatJsonData(null)).toEqual(['null'])
|
|
})
|
|
|
|
it('formats string with quotes', () => {
|
|
expect(formatJsonData('hello')).toEqual(['"hello"'])
|
|
})
|
|
|
|
it('formats number and boolean', () => {
|
|
expect(formatJsonData(42)).toEqual(['42'])
|
|
expect(formatJsonData(true)).toEqual(['true'])
|
|
})
|
|
|
|
it('formats empty array and object', () => {
|
|
expect(formatJsonData([])).toEqual(['[]'])
|
|
expect(formatJsonData({})).toEqual(['{}'])
|
|
})
|
|
})
|
|
|
|
describe('compareJson (integration)', () => {
|
|
it('compares identical JSON text', () => {
|
|
const json = '{"a":1,"b":[1,2]}'
|
|
const result = compareJson(json, json)
|
|
expect(result.stats.same).toBeGreaterThan(0)
|
|
expect(result.left.length).toBe(result.right.length)
|
|
expect(result.left.every((l, i) => l.lineNumber === i + 1)).toBe(true)
|
|
})
|
|
|
|
it('throws on invalid JSON', () => {
|
|
expect(() => compareJson('{invalid', '{}')).toThrow('JSON解析失败')
|
|
expect(() => compareJson('{}', '[unclosed')).toThrow('JSON解析失败')
|
|
})
|
|
|
|
it('throws on empty invalid input', () => {
|
|
expect(() => compareJson('', '{}')).toThrow('JSON解析失败')
|
|
})
|
|
|
|
it('detects value change in JSON', () => {
|
|
const result = compareJson('{"name":"Alice"}', '{"name":"Bob"}')
|
|
expect(result.stats.modify).toBeGreaterThan(0)
|
|
})
|
|
|
|
it('respects ignoreListOrder option', () => {
|
|
const a = '{"items":[1,2,3]}'
|
|
const b = '{"items":[3,2,1]}'
|
|
const ordered = compareJson(a, b, false)
|
|
const ignored = compareJson(a, b, true)
|
|
expect(ignored.stats.same).toBeGreaterThanOrEqual(ordered.stats.same)
|
|
})
|
|
|
|
it('handles deeply nested JSON', () => {
|
|
const a = JSON.stringify({ level1: { level2: { level3: { value: 1 } } } })
|
|
const b = JSON.stringify({ level1: { level2: { level3: { value: 2 } } } })
|
|
const result = compareJson(a, b)
|
|
expect(result.stats.modify).toBeGreaterThan(0)
|
|
})
|
|
|
|
it('handles JSON with unicode', () => {
|
|
const a = '{"msg":"你好"}'
|
|
const b = '{"msg":"世界"}'
|
|
const result = compareJson(a, b)
|
|
expect(result.left.length).toBeGreaterThan(0)
|
|
})
|
|
|
|
it('handles boolean and null values', () => {
|
|
const a = '{"flag":true,"empty":null}'
|
|
const b = '{"flag":false,"empty":null}'
|
|
const result = compareJson(a, b)
|
|
expect(result.stats.modify).toBeGreaterThan(0)
|
|
})
|
|
|
|
it('handles array length mismatch', () => {
|
|
const result = compareJson('[1,2]', '[1,2,3]')
|
|
expect(result.stats.insert + result.stats.modify).toBeGreaterThan(0)
|
|
})
|
|
|
|
it('handles empty objects comparison', () => {
|
|
const result = compareJson('{}', '{}')
|
|
expect(result.left.length).toBe(result.right.length)
|
|
expect(result.left.every(l => l.type === 'same')).toBe(true)
|
|
})
|
|
|
|
it('handles whitespace in JSON parse (valid JSON)', () => {
|
|
const result = compareJson(' { "a" : 1 } ', '{ "a" : 1 }')
|
|
expect(result.stats.same).toBeGreaterThan(0)
|
|
})
|
|
})
|
|
})
|