@@ -396,11 +396,12 @@ describe('ChatComponent', () => {
396396 } ) ;
397397
398398 it (
399- 'should not clear existing messages when new messages are loaded' ,
399+ 'should not clear existing messages or events when new messages are loaded' ,
400400 fakeAsync ( ( ) => {
401401 component . messages . set ( [
402402 { role : 'user' , text : 'existing message' } ,
403403 ] ) ;
404+ component . eventData . set ( 'event-old' , { id : 'event-old' } as any ) ;
404405 mockUiStateService . newMessagesLoadedResponse . next ( {
405406 items : events ,
406407 nextPageToken : '' ,
@@ -411,15 +412,17 @@ describe('ChatComponent', () => {
411412 expect ( messages [ 0 ] . text ) . toBe ( 'user message' ) ;
412413 expect ( messages [ 1 ] . text ) . toBe ( 'bot response' ) ;
413414 expect ( messages [ 2 ] . text ) . toBe ( 'existing message' ) ;
415+ expect ( component . eventData . has ( 'event-old' ) ) . toBeTrue ( ) ;
414416 } ) ) ;
415417
416418 it (
417- 'should clear existing messages when new messages are loaded for a different session' ,
419+ 'should clear existing messages and events when new messages are loaded for a different session' ,
418420 fakeAsync ( ( ) => {
419421 component . messages . set ( [
420422 { role : 'user' , text : 'existing message' } ,
421423 ] ) ;
422- component . sessionId = 'session-2' ; // change session
424+ component . eventData . set ( 'event-old' , { id : 'event-old' } as any ) ;
425+ component . sessionId = 'session-2' ; // change session
423426 mockUiStateService . newMessagesLoadedResponse . next ( {
424427 items : events ,
425428 nextPageToken : '' ,
@@ -429,6 +432,7 @@ describe('ChatComponent', () => {
429432 expect ( messages . length ) . toBe ( 2 ) ;
430433 expect ( messages [ 0 ] . text ) . toBe ( 'user message' ) ;
431434 expect ( messages [ 1 ] . text ) . toBe ( 'bot response' ) ;
435+ expect ( component . eventData . has ( 'event-old' ) ) . toBeFalse ( ) ;
432436 } ) ) ;
433437
434438 it ( 'should store events' , ( ) => {
@@ -992,8 +996,7 @@ describe('ChatComponent', () => {
992996 } ) ;
993997
994998 it (
995- 'should clear "q" param on send' ,
996- fakeAsync ( ( ) => {
999+ 'should clear "q" param on send' , fakeAsync ( ( ) => {
9971000 urlTree . queryParams = { [ INITIAL_USER_INPUT_QUERY_PARAM ] : 'hello' } ;
9981001 mockRouter . parseUrl . and . returnValue ( urlTree as any ) ;
9991002 mockLocation . path . and . returnValue ( '/?q=hello' ) ;
@@ -1009,8 +1012,7 @@ describe('ChatComponent', () => {
10091012 } ) ) ;
10101013
10111014 it (
1012- 'should not update URL if "q" param is missing' ,
1013- fakeAsync ( ( ) => {
1015+ 'should not update URL if "q" param is missing' , fakeAsync ( ( ) => {
10141016 urlTree . queryParams = { } ;
10151017 mockRouter . parseUrl . and . returnValue ( urlTree as any ) ;
10161018 mockLocation . path . and . returnValue ( '/?' ) ;
@@ -1027,57 +1029,61 @@ describe('ChatComponent', () => {
10271029 } ) ;
10281030
10291031 describe ( 'when event is an A2A response' , ( ) => {
1030- it ( 'should combine all A2UI data parts into a single message' , async ( ) => {
1031-
1032- const createA2uiPart = ( content : any ) => {
1033- const json = JSON . stringify ( {
1034- kind : 'data' ,
1035- metadata : { mimeType : A2UI_MIME_TYPE } ,
1036- data : content
1037- } ) ;
1038- return {
1039- inlineData : {
1040- mimeType : 'text/plain' ,
1041- data : btoa ( `${ A2A_DATA_PART_TAG_START } ${ json } ${ A2A_DATA_PART_TAG_END } ` )
1042- }
1043- } ;
1044- } ;
1032+ it (
1033+ 'should combine all A2UI data parts into a single message' ,
1034+ async ( ) => {
1035+ const createA2uiPart = ( content : any ) => {
1036+ const json = JSON . stringify ( {
1037+ kind : 'data' ,
1038+ metadata : { mimeType : A2UI_MIME_TYPE } ,
1039+ data : content
1040+ } ) ;
1041+ return {
1042+ inlineData : {
1043+ mimeType : 'text/plain' ,
1044+ data : btoa ( `${ A2A_DATA_PART_TAG_START } ${ json } ${
1045+ A2A_DATA_PART_TAG_END } `)
1046+ }
1047+ } ;
1048+ } ;
10451049
1046- const sseEvent = {
1047- id : 'event-1' ,
1048- author : 'bot' ,
1049- customMetadata : { 'a2a:response' : 'true' } ,
1050- content : {
1051- role : 'bot' ,
1052- parts : [
1053- { text : 'Prefix' } ,
1054- createA2uiPart ( { beginRendering : { id : '1' } } ) ,
1055- { text : 'Interim' } ,
1056- createA2uiPart ( { surfaceUpdate : { components : [ ] } } ) ,
1057- { text : 'Suffix' }
1058- ]
1059- } ,
1060- } ;
1050+ const sseEvent = {
1051+ id : 'event-1' ,
1052+ author : 'bot' ,
1053+ customMetadata : { 'a2a:response' : 'true' } ,
1054+ content : {
1055+ role : 'bot' ,
1056+ parts : [
1057+ { text : 'Prefix' } ,
1058+ createA2uiPart ( { beginRendering : { id : '1' } } ) ,
1059+ { text : 'Interim' } ,
1060+ createA2uiPart ( { surfaceUpdate : { components : [ ] } } ) ,
1061+ { text : 'Suffix' }
1062+ ]
1063+ } ,
1064+ } ;
10611065
1062- component . messages . set ( [ ] ) ;
1063- component . userInput = 'test message' ;
1064- await component . sendMessage (
1065- new KeyboardEvent ( 'keydown' , { key : 'Enter' } ) ) ;
1066- mockAgentService . runSseResponse . next ( sseEvent ) ;
1067- fixture . detectChanges ( ) ;
1066+ component . messages . set ( [ ] ) ;
1067+ component . userInput = 'test message' ;
1068+ await component . sendMessage (
1069+ new KeyboardEvent ( 'keydown' , { key : 'Enter' } ) ) ;
1070+ mockAgentService . runSseResponse . next ( sseEvent ) ;
1071+ fixture . detectChanges ( ) ;
10681072
1069- const botMessages = component . messages ( ) . filter ( m => m . role === 'bot' ) ;
1070- // Expectation: Prefix, Combined A2UI (at first A2UI pos), Interim, Suffix
1071- expect ( botMessages . length ) . toBe ( 4 ) ;
1072- expect ( botMessages [ 0 ] . text ) . toBe ( 'Prefix' ) ;
1073- // The combined A2UI message
1074- expect ( botMessages [ 1 ] . a2uiData ) . toEqual ( {
1075- beginRendering : { beginRendering : { id : '1' } } ,
1076- surfaceUpdate : { surfaceUpdate : { components : [ ] } }
1077- } ) ;
1078- expect ( botMessages [ 2 ] . text ) . toBe ( 'Interim' ) ;
1079- expect ( botMessages [ 3 ] . text ) . toBe ( 'Suffix' ) ;
1080- } ) ;
1073+ const botMessages =
1074+ component . messages ( ) . filter ( m => m . role === 'bot' ) ;
1075+ // Expectation: Prefix, Combined A2UI (at first A2UI pos),
1076+ // Interim, Suffix
1077+ expect ( botMessages . length ) . toBe ( 4 ) ;
1078+ expect ( botMessages [ 0 ] . text ) . toBe ( 'Prefix' ) ;
1079+ // The combined A2UI message
1080+ expect ( botMessages [ 1 ] . a2uiData ) . toEqual ( {
1081+ beginRendering : { beginRendering : { id : '1' } } ,
1082+ surfaceUpdate : { surfaceUpdate : { components : [ ] } }
1083+ } ) ;
1084+ expect ( botMessages [ 2 ] . text ) . toBe ( 'Interim' ) ;
1085+ expect ( botMessages [ 3 ] . text ) . toBe ( 'Suffix' ) ;
1086+ } ) ;
10811087 } ) ;
10821088
10831089
@@ -1494,12 +1500,12 @@ describe('ChatComponent', () => {
14941500 } ) ;
14951501
14961502 describe ( 'isA2aDataPart' , ( ) => {
1497-
14981503 it ( 'should return true for valid A2A data part' , ( ) => {
14991504 const part = {
15001505 inlineData : {
15011506 mimeType : 'text/plain' ,
1502- data : btoa ( `${ A2A_DATA_PART_TAG_START } {"test": true}${ A2A_DATA_PART_TAG_END } ` )
1507+ data : btoa ( `${ A2A_DATA_PART_TAG_START } {"test": true}${
1508+ A2A_DATA_PART_TAG_END } `)
15031509 }
15041510 } ;
15051511 expect ( ( component as any ) . isA2aDataPart ( part ) ) . toBeTrue ( ) ;
@@ -1522,34 +1528,28 @@ describe('ChatComponent', () => {
15221528
15231529 it ( 'should return false when tags are missing' , ( ) => {
15241530 const part = {
1525- inlineData : {
1526- mimeType : 'text/plain' ,
1527- data : btoa ( 'some random text' )
1528- }
1531+ inlineData : { mimeType : 'text/plain' , data : btoa ( 'some random text' ) }
15291532 } ;
15301533 expect ( ( component as any ) . isA2aDataPart ( part ) ) . toBeFalse ( ) ;
15311534 } ) ;
15321535 } ) ;
15331536
15341537 describe ( 'extractA2aDataPartJson' , ( ) => {
1535-
15361538 it ( 'should return parsed JSON for valid A2A data part' , ( ) => {
15371539 const data = { key : 'value' } ;
15381540 const part = {
15391541 inlineData : {
15401542 mimeType : 'text/plain' ,
1541- data : btoa ( `${ A2A_DATA_PART_TAG_START } ${ JSON . stringify ( data ) } ${ A2A_DATA_PART_TAG_END } ` )
1543+ data : btoa ( `${ A2A_DATA_PART_TAG_START } ${ JSON . stringify ( data ) } ${
1544+ A2A_DATA_PART_TAG_END } `)
15421545 }
15431546 } ;
15441547 expect ( ( component as any ) . extractA2aDataPartJson ( part ) ) . toEqual ( data ) ;
15451548 } ) ;
15461549
15471550 it ( 'should return null for non-A2A data part' , ( ) => {
15481551 const part = {
1549- inlineData : {
1550- mimeType : 'application/json' ,
1551- data : btoa ( '{}' )
1552- }
1552+ inlineData : { mimeType : 'application/json' , data : btoa ( '{}' ) }
15531553 } ;
15541554 expect ( ( component as any ) . extractA2aDataPartJson ( part ) ) . toBeNull ( ) ;
15551555 } ) ;
@@ -1558,23 +1558,24 @@ describe('ChatComponent', () => {
15581558 const part = {
15591559 inlineData : {
15601560 mimeType : 'text/plain' ,
1561- data : btoa ( `${ A2A_DATA_PART_TAG_START } {invalid-json${ A2A_DATA_PART_TAG_END } ` )
1561+ data : btoa (
1562+ `${ A2A_DATA_PART_TAG_START } {invalid-json${ A2A_DATA_PART_TAG_END } ` )
15621563 }
15631564 } ;
15641565 expect ( ( component as any ) . extractA2aDataPartJson ( part ) ) . toBeNull ( ) ;
15651566 } ) ;
15661567 } ) ;
15671568
15681569 describe ( 'combineA2uiDataParts' , ( ) => {
1569-
15701570 it ( 'should return empty array for empty input' , ( ) => {
15711571 expect ( ( component as any ) . combineA2uiDataParts ( [ ] ) ) . toEqual ( [ ] ) ;
15721572 } ) ;
15731573
1574- it ( 'should return original parts if no A2UI parts are present' , ( ) => {
1575- const parts = [ { text : 'hello' } , { text : 'world' } ] ;
1576- expect ( ( component as any ) . combineA2uiDataParts ( parts ) ) . toEqual ( parts ) ;
1577- } ) ;
1574+ it (
1575+ 'should return original parts if no A2UI parts are present' , ( ) => {
1576+ const parts = [ { text : 'hello' } , { text : 'world' } ] ;
1577+ expect ( ( component as any ) . combineA2uiDataParts ( parts ) ) . toEqual ( parts ) ;
1578+ } ) ;
15781579
15791580 it ( 'should combine multiple A2UI parts into the first one' , ( ) => {
15801581 const a2ui1 = {
@@ -1591,13 +1592,15 @@ describe('ChatComponent', () => {
15911592 const part1 = {
15921593 inlineData : {
15931594 mimeType : 'text/plain' ,
1594- data : btoa ( `${ A2A_DATA_PART_TAG_START } ${ JSON . stringify ( a2ui1 ) } ${ A2A_DATA_PART_TAG_END } ` )
1595+ data : btoa ( `${ A2A_DATA_PART_TAG_START } ${ JSON . stringify ( a2ui1 ) } ${
1596+ A2A_DATA_PART_TAG_END } `)
15951597 }
15961598 } ;
15971599 const part2 = {
15981600 inlineData : {
15991601 mimeType : 'text/plain' ,
1600- data : btoa ( `${ A2A_DATA_PART_TAG_START } ${ JSON . stringify ( a2ui2 ) } ${ A2A_DATA_PART_TAG_END } ` )
1602+ data : btoa ( `${ A2A_DATA_PART_TAG_START } ${ JSON . stringify ( a2ui2 ) } ${
1603+ A2A_DATA_PART_TAG_END } `)
16011604 }
16021605 } ;
16031606
@@ -1619,54 +1622,65 @@ describe('ChatComponent', () => {
16191622 const partA2UI = {
16201623 inlineData : {
16211624 mimeType : 'text/plain' ,
1622- data : btoa ( `${ A2A_DATA_PART_TAG_START } ${ JSON . stringify ( a2ui ) } ${ A2A_DATA_PART_TAG_END } ` )
1625+ data : btoa ( `${ A2A_DATA_PART_TAG_START } ${ JSON . stringify ( a2ui ) } ${
1626+ A2A_DATA_PART_TAG_END } `)
16231627 }
16241628 } ;
16251629 const partText = { text : 'hello' } ;
16261630
1627- const result = ( component as any ) . combineA2uiDataParts ( [ partText , partA2UI , partText ] ) ;
1631+ const result = ( component as any ) . combineA2uiDataParts ( [
1632+ partText , partA2UI , partText
1633+ ] ) ;
16281634 expect ( result . length ) . toBe ( 3 ) ;
16291635 expect ( result [ 0 ] ) . toBe ( partText ) ;
16301636 expect ( result [ 2 ] ) . toBe ( partText ) ;
1631- // The middle one should be the combined one (which is essentially partA2UI but modified/recreated)
1637+ // The middle one should be the combined one (which is essentially
1638+ // partA2UI but modified/recreated)
16321639 const combinedJson = ( component as any ) . extractA2aDataPartJson ( result [ 1 ] ) ;
16331640 expect ( combinedJson . data ) . toEqual ( [ a2ui ] ) ;
16341641 } ) ;
16351642
1636- it ( 'should handle mixed content (Text + A2UI + Text + A2UI) correctly' , ( ) => {
1637- const a2ui1 = {
1638- kind : 'data' ,
1639- metadata : { mimeType : A2UI_MIME_TYPE } ,
1640- data : { id : 1 }
1641- } ;
1642- const a2ui2 = {
1643- kind : 'data' ,
1644- metadata : { mimeType : A2UI_MIME_TYPE } ,
1645- data : { id : 2 }
1646- } ;
1647-
1648- const partA2UI1 = {
1649- inlineData : {
1650- mimeType : 'text/plain' ,
1651- data : btoa ( `${ A2A_DATA_PART_TAG_START } ${ JSON . stringify ( a2ui1 ) } ${ A2A_DATA_PART_TAG_END } ` )
1652- }
1653- } ;
1654- const partA2UI2 = {
1655- inlineData : {
1656- mimeType : 'text/plain' ,
1657- data : btoa ( `${ A2A_DATA_PART_TAG_START } ${ JSON . stringify ( a2ui2 ) } ${ A2A_DATA_PART_TAG_END } ` )
1658- }
1659- } ;
1660- const partText1 = { text : 'start' } ;
1661- const partText2 = { text : 'middle' } ;
1662-
1663- const result = ( component as any ) . combineA2uiDataParts ( [ partText1 , partA2UI1 , partText2 , partA2UI2 ] ) ;
1664- expect ( result . length ) . toBe ( 3 ) ;
1665- expect ( result [ 0 ] ) . toBe ( partText1 ) ;
1666- expect ( result [ 2 ] ) . toBe ( partText2 ) ;
1643+ it (
1644+ 'should handle mixed content (Text + A2UI + Text + A2UI) correctly' ,
1645+ ( ) => {
1646+ const a2ui1 = {
1647+ kind : 'data' ,
1648+ metadata : { mimeType : A2UI_MIME_TYPE } ,
1649+ data : { id : 1 }
1650+ } ;
1651+ const a2ui2 = {
1652+ kind : 'data' ,
1653+ metadata : { mimeType : A2UI_MIME_TYPE } ,
1654+ data : { id : 2 }
1655+ } ;
16671656
1668- const combinedJson = ( component as any ) . extractA2aDataPartJson ( result [ 1 ] ) ;
1669- expect ( combinedJson . data ) . toEqual ( [ a2ui1 , a2ui2 ] ) ;
1670- } ) ;
1657+ const partA2UI1 = {
1658+ inlineData : {
1659+ mimeType : 'text/plain' ,
1660+ data : btoa ( `${ A2A_DATA_PART_TAG_START } ${ JSON . stringify ( a2ui1 ) } ${
1661+ A2A_DATA_PART_TAG_END } `)
1662+ }
1663+ } ;
1664+ const partA2UI2 = {
1665+ inlineData : {
1666+ mimeType : 'text/plain' ,
1667+ data : btoa ( `${ A2A_DATA_PART_TAG_START } ${ JSON . stringify ( a2ui2 ) } ${
1668+ A2A_DATA_PART_TAG_END } `)
1669+ }
1670+ } ;
1671+ const partText1 = { text : 'start' } ;
1672+ const partText2 = { text : 'middle' } ;
1673+
1674+ const result = ( component as any ) . combineA2uiDataParts ( [
1675+ partText1 , partA2UI1 , partText2 , partA2UI2
1676+ ] ) ;
1677+ expect ( result . length ) . toBe ( 3 ) ;
1678+ expect ( result [ 0 ] ) . toBe ( partText1 ) ;
1679+ expect ( result [ 2 ] ) . toBe ( partText2 ) ;
1680+
1681+ const combinedJson =
1682+ ( component as any ) . extractA2aDataPartJson ( result [ 1 ] ) ;
1683+ expect ( combinedJson . data ) . toEqual ( [ a2ui1 , a2ui2 ] ) ;
1684+ } ) ;
16711685 } ) ;
16721686} ) ;
0 commit comments