Skip to content

Commit b25202f

Browse files
google-genai-botcopybara-github
authored andcommitted
ADK changes
PiperOrigin-RevId: 861128617
1 parent df412b5 commit b25202f

File tree

2 files changed

+150
-126
lines changed

2 files changed

+150
-126
lines changed

src/app/components/chat/chat.component.spec.ts

Lines changed: 126 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)