188 lines
4.4 KiB
TypeScript
188 lines
4.4 KiB
TypeScript
import type { IDiffResult, IDiffChange } from '../dist_ts_tapbundle_protocol/index.js';
|
|
|
|
/**
|
|
* Generate a diff between two values
|
|
*/
|
|
export function generateDiff(expected: any, actual: any, context: number = 3): IDiffResult | null {
|
|
// Handle same values
|
|
if (expected === actual) {
|
|
return null;
|
|
}
|
|
|
|
// Determine diff type based on values
|
|
if (typeof expected === 'string' && typeof actual === 'string') {
|
|
return generateStringDiff(expected, actual, context);
|
|
} else if (Array.isArray(expected) && Array.isArray(actual)) {
|
|
return generateArrayDiff(expected, actual);
|
|
} else if (expected && actual && typeof expected === 'object' && typeof actual === 'object') {
|
|
return generateObjectDiff(expected, actual);
|
|
} else {
|
|
return generatePrimitiveDiff(expected, actual);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate diff for primitive values
|
|
*/
|
|
function generatePrimitiveDiff(expected: any, actual: any): IDiffResult {
|
|
return {
|
|
type: 'primitive',
|
|
changes: [{
|
|
type: 'modify',
|
|
oldValue: expected,
|
|
newValue: actual
|
|
}]
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Generate diff for strings (line-by-line)
|
|
*/
|
|
function generateStringDiff(expected: string, actual: string, context: number): IDiffResult {
|
|
const expectedLines = expected.split('\n');
|
|
const actualLines = actual.split('\n');
|
|
const changes: IDiffChange[] = [];
|
|
|
|
// Simple line-by-line diff
|
|
const maxLines = Math.max(expectedLines.length, actualLines.length);
|
|
|
|
for (let i = 0; i < maxLines; i++) {
|
|
const expectedLine = expectedLines[i];
|
|
const actualLine = actualLines[i];
|
|
|
|
if (expectedLine === undefined) {
|
|
changes.push({
|
|
type: 'add',
|
|
line: i,
|
|
content: actualLine
|
|
});
|
|
} else if (actualLine === undefined) {
|
|
changes.push({
|
|
type: 'remove',
|
|
line: i,
|
|
content: expectedLine
|
|
});
|
|
} else if (expectedLine !== actualLine) {
|
|
changes.push({
|
|
type: 'remove',
|
|
line: i,
|
|
content: expectedLine
|
|
});
|
|
changes.push({
|
|
type: 'add',
|
|
line: i,
|
|
content: actualLine
|
|
});
|
|
}
|
|
}
|
|
|
|
return {
|
|
type: 'string',
|
|
changes,
|
|
context
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Generate diff for arrays
|
|
*/
|
|
function generateArrayDiff(expected: any[], actual: any[]): IDiffResult {
|
|
const changes: IDiffChange[] = [];
|
|
const maxLength = Math.max(expected.length, actual.length);
|
|
|
|
for (let i = 0; i < maxLength; i++) {
|
|
const expectedItem = expected[i];
|
|
const actualItem = actual[i];
|
|
|
|
if (i >= expected.length) {
|
|
changes.push({
|
|
type: 'add',
|
|
path: [String(i)],
|
|
newValue: actualItem
|
|
});
|
|
} else if (i >= actual.length) {
|
|
changes.push({
|
|
type: 'remove',
|
|
path: [String(i)],
|
|
oldValue: expectedItem
|
|
});
|
|
} else if (!deepEqual(expectedItem, actualItem)) {
|
|
changes.push({
|
|
type: 'modify',
|
|
path: [String(i)],
|
|
oldValue: expectedItem,
|
|
newValue: actualItem
|
|
});
|
|
}
|
|
}
|
|
|
|
return {
|
|
type: 'array',
|
|
changes
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Generate diff for objects
|
|
*/
|
|
function generateObjectDiff(expected: any, actual: any): IDiffResult {
|
|
const changes: IDiffChange[] = [];
|
|
const allKeys = new Set([...Object.keys(expected), ...Object.keys(actual)]);
|
|
|
|
for (const key of allKeys) {
|
|
const expectedValue = expected[key];
|
|
const actualValue = actual[key];
|
|
|
|
if (!(key in expected)) {
|
|
changes.push({
|
|
type: 'add',
|
|
path: [key],
|
|
newValue: actualValue
|
|
});
|
|
} else if (!(key in actual)) {
|
|
changes.push({
|
|
type: 'remove',
|
|
path: [key],
|
|
oldValue: expectedValue
|
|
});
|
|
} else if (!deepEqual(expectedValue, actualValue)) {
|
|
changes.push({
|
|
type: 'modify',
|
|
path: [key],
|
|
oldValue: expectedValue,
|
|
newValue: actualValue
|
|
});
|
|
}
|
|
}
|
|
|
|
return {
|
|
type: 'object',
|
|
changes
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Deep equality check
|
|
*/
|
|
function deepEqual(a: any, b: any): boolean {
|
|
if (a === b) return true;
|
|
|
|
if (a === null || b === null) return false;
|
|
if (typeof a !== typeof b) return false;
|
|
|
|
if (typeof a === 'object') {
|
|
if (Array.isArray(a) && Array.isArray(b)) {
|
|
if (a.length !== b.length) return false;
|
|
return a.every((item, index) => deepEqual(item, b[index]));
|
|
}
|
|
|
|
const keysA = Object.keys(a);
|
|
const keysB = Object.keys(b);
|
|
|
|
if (keysA.length !== keysB.length) return false;
|
|
|
|
return keysA.every(key => deepEqual(a[key], b[key]));
|
|
}
|
|
|
|
return false;
|
|
} |