@@ -7,7 +7,11 @@ import {
77} from '@sim/testing'
88import { describe , expect , it } from 'vitest'
99import type { WorkflowState } from '@/stores/workflows/workflow/types'
10- import { hasWorkflowChanged } from './compare'
10+ import {
11+ formatDiffSummaryForDescription ,
12+ generateWorkflowDiffSummary ,
13+ hasWorkflowChanged ,
14+ } from './compare'
1115
1216/**
1317 * Type helper for converting test workflow state to app workflow state.
@@ -2735,3 +2739,299 @@ describe('hasWorkflowChanged', () => {
27352739 } )
27362740 } )
27372741} )
2742+
2743+ describe ( 'generateWorkflowDiffSummary' , ( ) => {
2744+ describe ( 'Basic Cases' , ( ) => {
2745+ it . concurrent ( 'should return hasChanges=true when previousState is null' , ( ) => {
2746+ const currentState = createWorkflowState ( {
2747+ blocks : { block1 : createBlock ( 'block1' ) } ,
2748+ } )
2749+ const result = generateWorkflowDiffSummary ( currentState , null )
2750+ expect ( result . hasChanges ) . toBe ( true )
2751+ expect ( result . addedBlocks ) . toHaveLength ( 1 )
2752+ expect ( result . addedBlocks [ 0 ] . id ) . toBe ( 'block1' )
2753+ } )
2754+
2755+ it . concurrent ( 'should return hasChanges=false for identical states' , ( ) => {
2756+ const state = createWorkflowState ( {
2757+ blocks : { block1 : createBlock ( 'block1' ) } ,
2758+ } )
2759+ const result = generateWorkflowDiffSummary ( state , state )
2760+ expect ( result . hasChanges ) . toBe ( false )
2761+ expect ( result . addedBlocks ) . toHaveLength ( 0 )
2762+ expect ( result . removedBlocks ) . toHaveLength ( 0 )
2763+ expect ( result . modifiedBlocks ) . toHaveLength ( 0 )
2764+ } )
2765+ } )
2766+
2767+ describe ( 'Block Changes' , ( ) => {
2768+ it . concurrent ( 'should detect added blocks' , ( ) => {
2769+ const previousState = createWorkflowState ( {
2770+ blocks : { block1 : createBlock ( 'block1' ) } ,
2771+ } )
2772+ const currentState = createWorkflowState ( {
2773+ blocks : {
2774+ block1 : createBlock ( 'block1' ) ,
2775+ block2 : createBlock ( 'block2' ) ,
2776+ } ,
2777+ } )
2778+ const result = generateWorkflowDiffSummary ( currentState , previousState )
2779+ expect ( result . hasChanges ) . toBe ( true )
2780+ expect ( result . addedBlocks ) . toHaveLength ( 1 )
2781+ expect ( result . addedBlocks [ 0 ] . id ) . toBe ( 'block2' )
2782+ } )
2783+
2784+ it . concurrent ( 'should detect removed blocks' , ( ) => {
2785+ const previousState = createWorkflowState ( {
2786+ blocks : {
2787+ block1 : createBlock ( 'block1' ) ,
2788+ block2 : createBlock ( 'block2' ) ,
2789+ } ,
2790+ } )
2791+ const currentState = createWorkflowState ( {
2792+ blocks : { block1 : createBlock ( 'block1' ) } ,
2793+ } )
2794+ const result = generateWorkflowDiffSummary ( currentState , previousState )
2795+ expect ( result . hasChanges ) . toBe ( true )
2796+ expect ( result . removedBlocks ) . toHaveLength ( 1 )
2797+ expect ( result . removedBlocks [ 0 ] . id ) . toBe ( 'block2' )
2798+ } )
2799+
2800+ it . concurrent ( 'should detect modified blocks with field changes' , ( ) => {
2801+ const previousState = createWorkflowState ( {
2802+ blocks : {
2803+ block1 : createBlock ( 'block1' , {
2804+ subBlocks : { model : { id : 'model' , type : 'dropdown' , value : 'gpt-4o' } } ,
2805+ } ) ,
2806+ } ,
2807+ } )
2808+ const currentState = createWorkflowState ( {
2809+ blocks : {
2810+ block1 : createBlock ( 'block1' , {
2811+ subBlocks : { model : { id : 'model' , type : 'dropdown' , value : 'claude-sonnet' } } ,
2812+ } ) ,
2813+ } ,
2814+ } )
2815+ const result = generateWorkflowDiffSummary ( currentState , previousState )
2816+ expect ( result . hasChanges ) . toBe ( true )
2817+ expect ( result . modifiedBlocks ) . toHaveLength ( 1 )
2818+ expect ( result . modifiedBlocks [ 0 ] . id ) . toBe ( 'block1' )
2819+ expect ( result . modifiedBlocks [ 0 ] . changes . length ) . toBeGreaterThan ( 0 )
2820+ const modelChange = result . modifiedBlocks [ 0 ] . changes . find ( ( c ) => c . field === 'model' )
2821+ expect ( modelChange ) . toBeDefined ( )
2822+ expect ( modelChange ?. oldValue ) . toBe ( 'gpt-4o' )
2823+ expect ( modelChange ?. newValue ) . toBe ( 'claude-sonnet' )
2824+ } )
2825+ } )
2826+
2827+ describe ( 'Edge Changes' , ( ) => {
2828+ it . concurrent ( 'should detect added edges' , ( ) => {
2829+ const previousState = createWorkflowState ( {
2830+ blocks : {
2831+ block1 : createBlock ( 'block1' ) ,
2832+ block2 : createBlock ( 'block2' ) ,
2833+ } ,
2834+ edges : [ ] ,
2835+ } )
2836+ const currentState = createWorkflowState ( {
2837+ blocks : {
2838+ block1 : createBlock ( 'block1' ) ,
2839+ block2 : createBlock ( 'block2' ) ,
2840+ } ,
2841+ edges : [ { id : 'e1' , source : 'block1' , target : 'block2' } ] ,
2842+ } )
2843+ const result = generateWorkflowDiffSummary ( currentState , previousState )
2844+ expect ( result . hasChanges ) . toBe ( true )
2845+ expect ( result . edgeChanges . added ) . toBe ( 1 )
2846+ expect ( result . edgeChanges . removed ) . toBe ( 0 )
2847+ } )
2848+
2849+ it . concurrent ( 'should detect removed edges' , ( ) => {
2850+ const previousState = createWorkflowState ( {
2851+ blocks : {
2852+ block1 : createBlock ( 'block1' ) ,
2853+ block2 : createBlock ( 'block2' ) ,
2854+ } ,
2855+ edges : [ { id : 'e1' , source : 'block1' , target : 'block2' } ] ,
2856+ } )
2857+ const currentState = createWorkflowState ( {
2858+ blocks : {
2859+ block1 : createBlock ( 'block1' ) ,
2860+ block2 : createBlock ( 'block2' ) ,
2861+ } ,
2862+ edges : [ ] ,
2863+ } )
2864+ const result = generateWorkflowDiffSummary ( currentState , previousState )
2865+ expect ( result . hasChanges ) . toBe ( true )
2866+ expect ( result . edgeChanges . added ) . toBe ( 0 )
2867+ expect ( result . edgeChanges . removed ) . toBe ( 1 )
2868+ } )
2869+ } )
2870+
2871+ describe ( 'Variable Changes' , ( ) => {
2872+ it . concurrent ( 'should detect added variables' , ( ) => {
2873+ const previousState = createWorkflowState ( {
2874+ blocks : { block1 : createBlock ( 'block1' ) } ,
2875+ variables : { } ,
2876+ } )
2877+ const currentState = createWorkflowState ( {
2878+ blocks : { block1 : createBlock ( 'block1' ) } ,
2879+ variables : { var1 : { id : 'var1' , name : 'test' , type : 'string' , value : 'hello' } } ,
2880+ } )
2881+ const result = generateWorkflowDiffSummary ( currentState , previousState )
2882+ expect ( result . hasChanges ) . toBe ( true )
2883+ expect ( result . variableChanges . added ) . toBe ( 1 )
2884+ } )
2885+
2886+ it . concurrent ( 'should detect modified variables' , ( ) => {
2887+ const previousState = createWorkflowState ( {
2888+ blocks : { block1 : createBlock ( 'block1' ) } ,
2889+ variables : { var1 : { id : 'var1' , name : 'test' , type : 'string' , value : 'hello' } } ,
2890+ } )
2891+ const currentState = createWorkflowState ( {
2892+ blocks : { block1 : createBlock ( 'block1' ) } ,
2893+ variables : { var1 : { id : 'var1' , name : 'test' , type : 'string' , value : 'world' } } ,
2894+ } )
2895+ const result = generateWorkflowDiffSummary ( currentState , previousState )
2896+ expect ( result . hasChanges ) . toBe ( true )
2897+ expect ( result . variableChanges . modified ) . toBe ( 1 )
2898+ } )
2899+ } )
2900+
2901+ describe ( 'Consistency with hasWorkflowChanged' , ( ) => {
2902+ it . concurrent ( 'hasChanges should match hasWorkflowChanged result' , ( ) => {
2903+ const state1 = createWorkflowState ( {
2904+ blocks : { block1 : createBlock ( 'block1' ) } ,
2905+ } )
2906+ const state2 = createWorkflowState ( {
2907+ blocks : {
2908+ block1 : createBlock ( 'block1' , {
2909+ subBlocks : { prompt : { id : 'prompt' , type : 'long-input' , value : 'new value' } } ,
2910+ } ) ,
2911+ } ,
2912+ } )
2913+
2914+ const diffResult = generateWorkflowDiffSummary ( state2 , state1 )
2915+ const hasChangedResult = hasWorkflowChanged ( state2 , state1 )
2916+
2917+ expect ( diffResult . hasChanges ) . toBe ( hasChangedResult )
2918+ } )
2919+
2920+ it . concurrent ( 'should return same result as hasWorkflowChanged for no changes' , ( ) => {
2921+ const state = createWorkflowState ( {
2922+ blocks : { block1 : createBlock ( 'block1' ) } ,
2923+ } )
2924+
2925+ const diffResult = generateWorkflowDiffSummary ( state , state )
2926+ const hasChangedResult = hasWorkflowChanged ( state , state )
2927+
2928+ expect ( diffResult . hasChanges ) . toBe ( hasChangedResult )
2929+ expect ( diffResult . hasChanges ) . toBe ( false )
2930+ } )
2931+ } )
2932+ } )
2933+
2934+ describe ( 'formatDiffSummaryForDescription' , ( ) => {
2935+ it . concurrent ( 'should return no changes message when hasChanges is false' , ( ) => {
2936+ const summary = {
2937+ addedBlocks : [ ] ,
2938+ removedBlocks : [ ] ,
2939+ modifiedBlocks : [ ] ,
2940+ edgeChanges : { added : 0 , removed : 0 } ,
2941+ loopChanges : { added : 0 , removed : 0 } ,
2942+ parallelChanges : { added : 0 , removed : 0 } ,
2943+ variableChanges : { added : 0 , removed : 0 , modified : 0 } ,
2944+ hasChanges : false ,
2945+ }
2946+ const result = formatDiffSummaryForDescription ( summary )
2947+ expect ( result ) . toContain ( 'No structural changes' )
2948+ } )
2949+
2950+ it . concurrent ( 'should format added blocks' , ( ) => {
2951+ const summary = {
2952+ addedBlocks : [ { id : 'block1' , type : 'agent' , name : 'My Agent' } ] ,
2953+ removedBlocks : [ ] ,
2954+ modifiedBlocks : [ ] ,
2955+ edgeChanges : { added : 0 , removed : 0 } ,
2956+ loopChanges : { added : 0 , removed : 0 } ,
2957+ parallelChanges : { added : 0 , removed : 0 } ,
2958+ variableChanges : { added : 0 , removed : 0 , modified : 0 } ,
2959+ hasChanges : true ,
2960+ }
2961+ const result = formatDiffSummaryForDescription ( summary )
2962+ expect ( result ) . toContain ( 'Added block: My Agent (agent)' )
2963+ } )
2964+
2965+ it . concurrent ( 'should format removed blocks' , ( ) => {
2966+ const summary = {
2967+ addedBlocks : [ ] ,
2968+ removedBlocks : [ { id : 'block1' , type : 'function' , name : 'Old Function' } ] ,
2969+ modifiedBlocks : [ ] ,
2970+ edgeChanges : { added : 0 , removed : 0 } ,
2971+ loopChanges : { added : 0 , removed : 0 } ,
2972+ parallelChanges : { added : 0 , removed : 0 } ,
2973+ variableChanges : { added : 0 , removed : 0 , modified : 0 } ,
2974+ hasChanges : true ,
2975+ }
2976+ const result = formatDiffSummaryForDescription ( summary )
2977+ expect ( result ) . toContain ( 'Removed block: Old Function (function)' )
2978+ } )
2979+
2980+ it . concurrent ( 'should format modified blocks with field changes' , ( ) => {
2981+ const summary = {
2982+ addedBlocks : [ ] ,
2983+ removedBlocks : [ ] ,
2984+ modifiedBlocks : [
2985+ {
2986+ id : 'block1' ,
2987+ type : 'agent' ,
2988+ name : 'Agent 1' ,
2989+ changes : [ { field : 'model' , oldValue : 'gpt-4o' , newValue : 'claude-sonnet' } ] ,
2990+ } ,
2991+ ] ,
2992+ edgeChanges : { added : 0 , removed : 0 } ,
2993+ loopChanges : { added : 0 , removed : 0 } ,
2994+ parallelChanges : { added : 0 , removed : 0 } ,
2995+ variableChanges : { added : 0 , removed : 0 , modified : 0 } ,
2996+ hasChanges : true ,
2997+ }
2998+ const result = formatDiffSummaryForDescription ( summary )
2999+ expect ( result ) . toContain ( 'Modified Agent 1' )
3000+ expect ( result ) . toContain ( 'model' )
3001+ expect ( result ) . toContain ( 'gpt-4o' )
3002+ expect ( result ) . toContain ( 'claude-sonnet' )
3003+ } )
3004+
3005+ it . concurrent ( 'should format edge changes' , ( ) => {
3006+ const summary = {
3007+ addedBlocks : [ ] ,
3008+ removedBlocks : [ ] ,
3009+ modifiedBlocks : [ ] ,
3010+ edgeChanges : { added : 2 , removed : 1 } ,
3011+ loopChanges : { added : 0 , removed : 0 } ,
3012+ parallelChanges : { added : 0 , removed : 0 } ,
3013+ variableChanges : { added : 0 , removed : 0 , modified : 0 } ,
3014+ hasChanges : true ,
3015+ }
3016+ const result = formatDiffSummaryForDescription ( summary )
3017+ expect ( result ) . toContain ( 'Added 2 connection(s)' )
3018+ expect ( result ) . toContain ( 'Removed 1 connection(s)' )
3019+ } )
3020+
3021+ it . concurrent ( 'should format variable changes' , ( ) => {
3022+ const summary = {
3023+ addedBlocks : [ ] ,
3024+ removedBlocks : [ ] ,
3025+ modifiedBlocks : [ ] ,
3026+ edgeChanges : { added : 0 , removed : 0 } ,
3027+ loopChanges : { added : 0 , removed : 0 } ,
3028+ parallelChanges : { added : 0 , removed : 0 } ,
3029+ variableChanges : { added : 1 , removed : 0 , modified : 2 } ,
3030+ hasChanges : true ,
3031+ }
3032+ const result = formatDiffSummaryForDescription ( summary )
3033+ expect ( result ) . toContain ( 'Variables:' )
3034+ expect ( result ) . toContain ( '1 added' )
3035+ expect ( result ) . toContain ( '2 modified' )
3036+ } )
3037+ } )
0 commit comments