Skip to content

Commit d9a23be

Browse files
ergunshDevtools-frontend LUCI CQ
authored andcommitted
[AiAssistance] Move markdown renderer tests in a separate file
Drive-by: * Rename `Freestyler` references to be `AiAssistance` or `EvaluateAction` based on what is imported. Bug: none Change-Id: I87d7cc5646cc25097243eab4fa68f129c61f5149 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6264204 Commit-Queue: Alex Rudenko <[email protected]> Commit-Queue: Ergün Erdoğmuş <[email protected]> Auto-Submit: Ergün Erdoğmuş <[email protected]> Reviewed-by: Alex Rudenko <[email protected]>
1 parent 92b8346 commit d9a23be

File tree

7 files changed

+126
-123
lines changed

7 files changed

+126
-123
lines changed

front_end/panels/ai_assistance/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ ts_library("unittests") {
102102
"agents/PerformanceAgent.test.ts",
103103
"agents/StylingAgent.test.ts",
104104
"components/ChatView.test.ts",
105+
"components/MarkdownRendererWithCodeBlock.test.ts",
105106
"components/UserActionRow.test.ts",
106107
"data_formatters/FileFormatter.test.ts",
107108
"data_formatters/NetworkRequestFormatter.test.ts",

front_end/panels/ai_assistance/ChangeManager.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import * as SDK from '../../core/sdk/sdk.js';
66
import type * as Protocol from '../../generated/protocol.js';
77

8-
import * as Freestyler from './ai_assistance.js';
8+
import * as AiAssistance from './ai_assistance.js';
99

1010
describe('ChangeManager', () => {
1111
let styleSheetId = 0;
@@ -45,7 +45,7 @@ describe('ChangeManager', () => {
4545
}
4646

4747
it('can register a change', async () => {
48-
const changeManager = new Freestyler.ChangeManager();
48+
const changeManager = new AiAssistance.ChangeManager();
4949
const cssModel = createModel();
5050
await changeManager.addChange(cssModel, frameId, {
5151
groupId: agentId,
@@ -63,7 +63,7 @@ describe('ChangeManager', () => {
6363
});
6464

6565
it('can merge multiple changes with same className', async () => {
66-
const changeManager = new Freestyler.ChangeManager();
66+
const changeManager = new AiAssistance.ChangeManager();
6767
const cssModel = createModel();
6868
await changeManager.addChange(cssModel, frameId, {
6969
groupId: agentId,
@@ -92,7 +92,7 @@ describe('ChangeManager', () => {
9292
});
9393

9494
it('can register multiple changes with the same selector', async () => {
95-
const changeManager = new Freestyler.ChangeManager();
95+
const changeManager = new AiAssistance.ChangeManager();
9696
const cssModel = createModel();
9797
await changeManager.addChange(cssModel, frameId, {
9898
groupId: agentId,
@@ -120,7 +120,7 @@ describe('ChangeManager', () => {
120120
});
121121

122122
it('creates a stylesheet per frame', async () => {
123-
const changeManager = new Freestyler.ChangeManager();
123+
const changeManager = new AiAssistance.ChangeManager();
124124
const cssModel = createModel();
125125
await changeManager.addChange(cssModel, frameId, {
126126
groupId: agentId,
@@ -151,7 +151,7 @@ describe('ChangeManager', () => {
151151
});
152152

153153
it('can clear changes', async () => {
154-
const changeManager = new Freestyler.ChangeManager();
154+
const changeManager = new AiAssistance.ChangeManager();
155155
let cssModel = createModel();
156156
await changeManager.addChange(cssModel, frameId, {
157157
groupId: agentId,
@@ -185,13 +185,13 @@ describe('ChangeManager', () => {
185185

186186
describe('format changes', () => {
187187
it('returns empty string when there are no changes from the given agent', async () => {
188-
const changeManager = new Freestyler.ChangeManager();
188+
const changeManager = new AiAssistance.ChangeManager();
189189

190190
assert.strictEqual(changeManager.formatChanges(agentId), '');
191191
});
192192

193193
it('returns formatted changes for an agent without `.ai-style-change` classes', async () => {
194-
const changeManager = new Freestyler.ChangeManager();
194+
const changeManager = new AiAssistance.ChangeManager();
195195
const cssModel = createModel();
196196

197197
await changeManager.addChange(cssModel, frameId, {

front_end/panels/ai_assistance/EvaluateAction.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as SDK from '../../core/sdk/sdk.js';
66
import * as Protocol from '../../generated/protocol.js';
77
import {describeWithRealConnection, getExecutionContext} from '../../testing/RealConnection.js';
88

9-
import * as Freestyler from './EvaluateAction.js';
9+
import * as EvaluateAction from './EvaluateAction.js';
1010

1111
describe('FreestylerEvaluateAction', () => {
1212
describe('error handling', () => {
@@ -21,7 +21,7 @@ describe('FreestylerEvaluateAction', () => {
2121
}
2222
executionContextStub.callFunctionOn.resolves(mockResult);
2323
executionContextStub.runtimeModel = sinon.createStubInstance(SDK.RuntimeModel.RuntimeModel);
24-
return Freestyler.EvaluateAction.execute('', [], executionContextStub, {throwOnSideEffect: false});
24+
return EvaluateAction.EvaluateAction.execute('', [], executionContextStub, {throwOnSideEffect: false});
2525
}
2626

2727
function mockRemoteObject(overrides: Partial<SDK.RemoteObject.RemoteObject> = {}): SDK.RemoteObject.RemoteObject {
@@ -77,7 +77,7 @@ describe('FreestylerEvaluateAction', () => {
7777
});
7878
assert.fail('not reachable');
7979
} catch (err) {
80-
assert.instanceOf(err, Freestyler.SideEffectError);
80+
assert.instanceOf(err, EvaluateAction.SideEffectError);
8181
assert.strictEqual(err.message, 'EvalError: Possible side-effect in debug-evaluate');
8282
}
8383
});
@@ -101,7 +101,7 @@ describe('FreestylerEvaluateAction', () => {
101101
return error;
102102
}
103103
}`;
104-
return Freestyler.EvaluateAction.execute(
104+
return EvaluateAction.EvaluateAction.execute(
105105
functionDeclaration, [], await executionContextForTest(), {throwOnSideEffect});
106106
}
107107

front_end/panels/ai_assistance/components/ChatView.test.ts

Lines changed: 14 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -6,101 +6,13 @@ import * as Host from '../../../core/host/host.js';
66
import * as Root from '../../../core/root/root.js';
77
import {renderElementIntoDOM} from '../../../testing/DOMHelpers.js';
88
import {describeWithEnvironment} from '../../../testing/EnvironmentHelpers.js';
9-
import * as Marked from '../../../third_party/marked/marked.js';
10-
import * as MarkdownView from '../../../ui/components/markdown_view/markdown_view.js';
11-
import * as Freestyler from '../ai_assistance.js';
12-
13-
const {MarkdownRendererWithCodeBlock} = Freestyler.FOR_TEST;
9+
import * as AiAssistance from '../ai_assistance.js';
1410

1511
describeWithEnvironment('ChatView', () => {
16-
describe('MarkdownRendererWithCodeBlock', () => {
17-
it('should transform code token for multiline code blocks with `css` language written in the first line', () => {
18-
const renderer = new MarkdownRendererWithCodeBlock();
19-
const templateForTokenStub =
20-
sinon.stub(MarkdownView.MarkdownView.MarkdownInsightRenderer.prototype, 'templateForToken');
21-
const codeBlock = `\`\`\`
22-
css
23-
* {
24-
color: red;
25-
}
26-
\`\`\``;
27-
const codeToken = Marked.Marked.lexer(codeBlock)[0] as Marked.Marked.Tokens.Code;
28-
assert.isEmpty(codeToken.lang);
29-
renderer.renderToken(codeToken);
30-
31-
sinon.assert.calledWith(templateForTokenStub, sinon.match({
32-
lang: 'css',
33-
text: `* {
34-
color: red;
35-
}`,
36-
}));
37-
});
38-
39-
describe('link/image stripping', () => {
40-
const linkCases = [
41-
'[link text](https://z.com)',
42-
'A response with [link text](https://z.com).',
43-
'[*link text*](https://z.com)',
44-
'[**text** `with code`](https://z.com).',
45-
'plain link https://z.com .',
46-
'link in quotes \'https://z.com\' .',
47-
];
48-
49-
const renderToElem = (string: string, renderer: MarkdownView.MarkdownView.MarkdownLitRenderer): Element => {
50-
const component = new MarkdownView.MarkdownView.MarkdownView();
51-
renderElementIntoDOM(component, {allowMultipleChildren: true});
52-
component.data = {tokens: Marked.Marked.lexer(string), renderer};
53-
assert.exists(component.shadowRoot?.firstElementChild);
54-
return component.shadowRoot.firstElementChild;
55-
};
56-
57-
it('strips links if stripLinks true', () => {
58-
const linklessRenderer = new MarkdownRendererWithCodeBlock({stripLinks: true});
59-
for (const linkCase of linkCases) {
60-
const elem = renderToElem(linkCase, linklessRenderer);
61-
assert.lengthOf(elem.querySelectorAll('a, x-link, devtools-link'), 0);
62-
assert.isFalse(['<a', '<x-link', '<devtools-link'].some(tagName => elem.outerHTML.includes(tagName)));
63-
assert.isOk(elem.textContent?.includes('( https://z.com )'), linkCase);
64-
}
65-
});
66-
67-
it('leaves links intact by default', () => {
68-
const linkfulRenderer = new MarkdownRendererWithCodeBlock();
69-
for (const linkCase of linkCases) {
70-
const elem = renderToElem(linkCase, linkfulRenderer);
71-
assert.lengthOf(elem.querySelectorAll('a, x-link, devtools-link'), 1);
72-
assert.isTrue(['<a', '<x-link', '<devtools-link'].some(tagName => elem.outerHTML.includes(tagName)));
73-
assert.isFalse(elem.textContent?.includes('( https://z.com )'));
74-
}
75-
});
76-
77-
const imageCases = [
78-
'![image alt](https://z.com/i.png)',
79-
'A response with ![image alt](https://z.com/i.png).',
80-
'![*image alt*](https://z.com/i.png)',
81-
'![**text** `with code`](https://z.com/i.png).',
82-
'plain image href https://z.com/i.png .',
83-
'link in quotes \'https://z.com/i.png\' .',
84-
];
85-
86-
it('strips images if stripLinks true', () => {
87-
const linklessRenderer = new MarkdownRendererWithCodeBlock({stripLinks: true});
88-
for (const imageCase of imageCases) {
89-
const elem = renderToElem(imageCase, linklessRenderer);
90-
assert.lengthOf(elem.querySelectorAll('a, x-link, devtools-link, img, devtools-markdown-image'), 0);
91-
assert.isFalse(['<a', '<x-link', '<devtools-link', '<img', '<devtools-markdown-image'].some(
92-
tagName => elem.outerHTML.includes(tagName)));
93-
94-
assert.isOk(elem.textContent?.includes('( https://z.com/i.png )'), imageCase);
95-
}
96-
});
97-
});
98-
});
99-
100-
function getProp(options: Partial<Freestyler.Props>): Freestyler.Props {
12+
function getProp(options: Partial<AiAssistance.Props>): AiAssistance.Props {
10113
const noop = () => {};
102-
const messages: Freestyler.ChatMessage[] = options.messages ?? [];
103-
const selectedContext = sinon.createStubInstance(Freestyler.NodeContext);
14+
const messages: AiAssistance.ChatMessage[] = options.messages ?? [];
15+
const selectedContext = sinon.createStubInstance(AiAssistance.NodeContext);
10416
selectedContext.getTitle.returns('');
10517
return {
10618
onTextSubmit: noop,
@@ -110,8 +22,8 @@ css
11022
onContextClick: noop,
11123
onNewConversation: noop,
11224
inspectElementToggled: false,
113-
state: Freestyler.State.CHAT_VIEW,
114-
agentType: Freestyler.AgentType.STYLING,
25+
state: AiAssistance.State.CHAT_VIEW,
26+
agentType: AiAssistance.AgentType.STYLING,
11527
aidaAvailability: Host.AidaClient.AidaAccessPreconditions.AVAILABLE,
11628
messages,
11729
selectedContext,
@@ -130,7 +42,7 @@ css
13042
const props = getProp({
13143
messages: [
13244
{
133-
entity: Freestyler.ChatMessageEntity.MODEL,
45+
entity: AiAssistance.ChatMessageEntity.MODEL,
13446
steps: [
13547
{
13648
isLoading: false,
@@ -145,7 +57,7 @@ css
14557
},
14658
],
14759
});
148-
const chat = new Freestyler.ChatView(props);
60+
const chat = new AiAssistance.ChatView(props);
14961
renderElementIntoDOM(chat);
15062

15163
const sideEffect = chat.shadowRoot!.querySelector('.side-effect-confirmation');
@@ -154,9 +66,9 @@ css
15466

15567
it('shows the disabled view when the state is CONSENT_VIEW', async () => {
15668
const props = getProp({
157-
state: Freestyler.State.CONSENT_VIEW,
69+
state: AiAssistance.State.CONSENT_VIEW,
15870
});
159-
const chat = new Freestyler.ChatView(props);
71+
const chat = new AiAssistance.ChatView(props);
16072
renderElementIntoDOM(chat);
16173

16274
const optIn = chat.shadowRoot?.querySelector('.disabled-view');
@@ -169,10 +81,10 @@ css
16981

17082
it('shows the disabled view when the AIDA is not available', async () => {
17183
const props = getProp({
172-
state: Freestyler.State.CHAT_VIEW,
84+
state: AiAssistance.State.CHAT_VIEW,
17385
aidaAvailability: Host.AidaClient.AidaAccessPreconditions.NO_INTERNET,
17486
});
175-
const chat = new Freestyler.ChatView(props);
87+
const chat = new AiAssistance.ChatView(props);
17688
renderElementIntoDOM(chat);
17789

17890
const optIn = chat.shadowRoot?.querySelector('.disabled-view');
@@ -201,7 +113,7 @@ css
201113
const props = getProp({
202114
agentType: undefined,
203115
});
204-
const chat = new Freestyler.ChatView(props);
116+
const chat = new AiAssistance.ChatView(props);
205117
renderElementIntoDOM(chat);
206118
const featureCards = chat.shadowRoot?.querySelectorAll('.feature-card');
207119
assert.isDefined(featureCards);
@@ -230,7 +142,7 @@ css
230142
const props = getProp({
231143
agentType: undefined,
232144
});
233-
const chat = new Freestyler.ChatView(props);
145+
const chat = new AiAssistance.ChatView(props);
234146
renderElementIntoDOM(chat);
235147
const featureCards = chat.shadowRoot?.querySelectorAll('.feature-card');
236148
assert.isDefined(featureCards);

front_end/panels/ai_assistance/components/ChatView.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1800,8 +1800,4 @@ declare global {
18001800
}
18011801
}
18021802

1803-
export const FOR_TEST = {
1804-
MarkdownRendererWithCodeBlock,
1805-
};
1806-
18071803
customElements.define('devtools-ai-chat-view', ChatView);
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright 2025 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import {renderElementIntoDOM} from '../../../testing/DOMHelpers.js';
6+
import {describeWithEnvironment} from '../../../testing/EnvironmentHelpers.js';
7+
import * as Marked from '../../../third_party/marked/marked.js';
8+
import * as MarkdownView from '../../../ui/components/markdown_view/markdown_view.js';
9+
import * as AiAssistance from '../ai_assistance.js';
10+
11+
describeWithEnvironment('MarkdownRendererWithCodeBlock', () => {
12+
it('should transform code token for multiline code blocks with `css` language written in the first line', () => {
13+
const renderer = new AiAssistance.MarkdownRendererWithCodeBlock();
14+
const templateForTokenStub =
15+
sinon.stub(MarkdownView.MarkdownView.MarkdownInsightRenderer.prototype, 'templateForToken');
16+
const codeBlock = `\`\`\`
17+
css
18+
* {
19+
color: red;
20+
}
21+
\`\`\``;
22+
const codeToken = Marked.Marked.lexer(codeBlock)[0] as Marked.Marked.Tokens.Code;
23+
assert.isEmpty(codeToken.lang);
24+
renderer.renderToken(codeToken);
25+
26+
sinon.assert.calledWith(templateForTokenStub, sinon.match({
27+
lang: 'css',
28+
text: `* {
29+
color: red;
30+
}`,
31+
}));
32+
});
33+
34+
describe('link/image stripping', () => {
35+
const linkCases = [
36+
'[link text](https://z.com)',
37+
'A response with [link text](https://z.com).',
38+
'[*link text*](https://z.com)',
39+
'[**text** `with code`](https://z.com).',
40+
'plain link https://z.com .',
41+
'link in quotes \'https://z.com\' .',
42+
];
43+
44+
const renderToElem = (string: string, renderer: MarkdownView.MarkdownView.MarkdownLitRenderer): Element => {
45+
const component = new MarkdownView.MarkdownView.MarkdownView();
46+
renderElementIntoDOM(component, {allowMultipleChildren: true});
47+
component.data = {tokens: Marked.Marked.lexer(string), renderer};
48+
assert.exists(component.shadowRoot?.firstElementChild);
49+
return component.shadowRoot.firstElementChild;
50+
};
51+
52+
it('strips links if stripLinks true', () => {
53+
const linklessRenderer = new AiAssistance.MarkdownRendererWithCodeBlock({stripLinks: true});
54+
for (const linkCase of linkCases) {
55+
const elem = renderToElem(linkCase, linklessRenderer);
56+
assert.lengthOf(elem.querySelectorAll('a, x-link, devtools-link'), 0);
57+
assert.isFalse(['<a', '<x-link', '<devtools-link'].some(tagName => elem.outerHTML.includes(tagName)));
58+
assert.isOk(elem.textContent?.includes('( https://z.com )'), linkCase);
59+
}
60+
});
61+
62+
it('leaves links intact by default', () => {
63+
const linkfulRenderer = new AiAssistance.MarkdownRendererWithCodeBlock();
64+
for (const linkCase of linkCases) {
65+
const elem = renderToElem(linkCase, linkfulRenderer);
66+
assert.lengthOf(elem.querySelectorAll('a, x-link, devtools-link'), 1);
67+
assert.isTrue(['<a', '<x-link', '<devtools-link'].some(tagName => elem.outerHTML.includes(tagName)));
68+
assert.isFalse(elem.textContent?.includes('( https://z.com )'));
69+
}
70+
});
71+
72+
const imageCases = [
73+
'![image alt](https://z.com/i.png)',
74+
'A response with ![image alt](https://z.com/i.png).',
75+
'![*image alt*](https://z.com/i.png)',
76+
'![**text** `with code`](https://z.com/i.png).',
77+
'plain image href https://z.com/i.png .',
78+
'link in quotes \'https://z.com/i.png\' .',
79+
];
80+
81+
it('strips images if stripLinks true', () => {
82+
const linklessRenderer = new AiAssistance.MarkdownRendererWithCodeBlock({stripLinks: true});
83+
for (const imageCase of imageCases) {
84+
const elem = renderToElem(imageCase, linklessRenderer);
85+
assert.lengthOf(elem.querySelectorAll('a, x-link, devtools-link, img, devtools-markdown-image'), 0);
86+
assert.isFalse(['<a', '<x-link', '<devtools-link', '<img', '<devtools-markdown-image'].some(
87+
tagName => elem.outerHTML.includes(tagName)));
88+
89+
assert.isOk(elem.textContent?.includes('( https://z.com/i.png )'), imageCase);
90+
}
91+
});
92+
});
93+
});

0 commit comments

Comments
 (0)