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;
 | |
| } |