|
4 | 4 | *--------------------------------------------------------------------------------------------*/ |
5 | 5 |
|
6 | 6 | import assert from 'assert'; |
| 7 | +import sinon from 'sinon'; |
7 | 8 | import { MarkdownString } from '../../../../../../base/common/htmlContent.js'; |
| 9 | +import { OperatingSystem } from '../../../../../../base/common/platform.js'; |
| 10 | +import { URI } from '../../../../../../base/common/uri.js'; |
8 | 11 | import { assertSnapshot } from '../../../../../../base/test/common/snapshot.js'; |
9 | 12 | import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; |
| 13 | +import { AGENT_HOST_LABEL_FORMATTER, agentHostAuthority, agentHostLabelFormatter, toAgentHostUri } from '../../../../../../platform/agentHost/common/agentHostUri.js'; |
| 14 | +import { IHoverService } from '../../../../../../platform/hover/browser/hover.js'; |
| 15 | +import { NullHoverService } from '../../../../../../platform/hover/test/browser/nullHoverService.js'; |
| 16 | +import { ILabelService } from '../../../../../../platform/label/common/label.js'; |
10 | 17 | import { ChatContentMarkdownRenderer } from '../../../browser/widget/chatContentMarkdownRenderer.js'; |
11 | 18 | import { workbenchInstantiationService } from '../../../../../test/browser/workbenchTestServices.js'; |
12 | 19 |
|
13 | 20 | suite('ChatMarkdownRenderer', () => { |
14 | 21 | const store = ensureNoDisposablesAreLeakedInTestSuite(); |
15 | 22 |
|
16 | 23 | let testRenderer: ChatContentMarkdownRenderer; |
| 24 | + let instantiationService: ReturnType<typeof workbenchInstantiationService>; |
17 | 25 | setup(() => { |
18 | | - const instantiationService = store.add(workbenchInstantiationService(undefined, store)); |
| 26 | + instantiationService = store.add(workbenchInstantiationService(undefined, store)); |
19 | 27 | testRenderer = instantiationService.createInstance(ChatContentMarkdownRenderer); |
20 | 28 | }); |
21 | 29 |
|
| 30 | + suite('link hovers', () => { |
| 31 | + let setupManagedHover: sinon.SinonSpy<Parameters<IHoverService['setupManagedHover']>, ReturnType<IHoverService['setupManagedHover']>>; |
| 32 | + |
| 33 | + setup(() => { |
| 34 | + setupManagedHover = sinon.spy(NullHoverService.setupManagedHover); |
| 35 | + instantiationService.stub(IHoverService, { ...NullHoverService, setupManagedHover }); |
| 36 | + store.add(instantiationService.get(ILabelService).registerFormatter(AGENT_HOST_LABEL_FORMATTER)); |
| 37 | + testRenderer = instantiationService.createInstance(ChatContentMarkdownRenderer); |
| 38 | + }); |
| 39 | + |
| 40 | + test('shows host paths for transformed and already mapped links without changing their targets', () => { |
| 41 | + const authority = agentHostAuthority('vscode-remote://wsl+Ubuntu'); |
| 42 | + store.add(instantiationService.get(ILabelService).registerFormatter(agentHostLabelFormatter(authority, OperatingSystem.Linux))); |
| 43 | + const file = URI.file('/home/user/my project/a&b.ts').with({ fragment: 'L42,7' }); |
| 44 | + const target = toAgentHostUri(file, authority).toString(); |
| 45 | + const links = [ |
| 46 | + { href: '/home/user/my%20project/a&b.ts:42:7', transformUri: () => target }, |
| 47 | + { href: target, transformUri: undefined }, |
| 48 | + ]; |
| 49 | + |
| 50 | + const actual = links.map(({ href, transformUri }) => { |
| 51 | + const result = store.add(testRenderer.render(new MarkdownString(`[file](${href})`), { transformUri })); |
| 52 | + const link = result.element.querySelector('a'); |
| 53 | + return { |
| 54 | + hover: setupManagedHover.lastCall.args[2], |
| 55 | + target: link?.dataset.href, |
| 56 | + text: link?.textContent, |
| 57 | + nativeTitle: link?.title, |
| 58 | + }; |
| 59 | + }); |
| 60 | + |
| 61 | + assert.deepStrictEqual(actual, links.map(() => ({ |
| 62 | + hover: '/home/user/my project/a&b.ts#L42,7', |
| 63 | + target, |
| 64 | + text: 'file', |
| 65 | + nativeTitle: '', |
| 66 | + }))); |
| 67 | + }); |
| 68 | + |
| 69 | + test('uses the remote host operating system for path formatting', () => { |
| 70 | + const labelService = instantiationService.get(ILabelService); |
| 71 | + store.add(labelService.registerFormatter(agentHostLabelFormatter('windows-host', OperatingSystem.Windows))); |
| 72 | + const target = toAgentHostUri(URI.file('C:/my project/file.ts'), 'windows-host').toString(); |
| 73 | + store.add(testRenderer.render(new MarkdownString(`[file](${target})`))); |
| 74 | + |
| 75 | + assert.strictEqual(setupManagedHover.lastCall.args[2], 'C:\\my project\\file.ts'); |
| 76 | + }); |
| 77 | + |
| 78 | + test('preserves explicit titles and ordinary file, external, and command link behavior', () => { |
| 79 | + const target = toAgentHostUri(URI.file('/home/user/file.ts'), 'remote-host').toString(); |
| 80 | + const file = URI.file('/my project/file.ts').with({ fragment: 'L7' }); |
| 81 | + const markdown = new MarkdownString(`[custom](${target} "Custom title") [file](${file}) [web](https://example.com/) [command](command:example)`, { isTrusted: true }); |
| 82 | + const result = store.add(testRenderer.render(markdown)); |
| 83 | + |
| 84 | + assert.deepStrictEqual({ |
| 85 | + hovers: setupManagedHover.getCalls().map(call => call.args[2]), |
| 86 | + targets: Array.from(result.element.querySelectorAll('a'), link => link.dataset.href), |
| 87 | + }, { |
| 88 | + hovers: ['Custom title', `${file.fsPath}#L7`, 'https://example.com/'], |
| 89 | + targets: [target, file.toString(), 'https://example.com/', 'command:example'], |
| 90 | + }); |
| 91 | + }); |
| 92 | + }); |
| 93 | + |
22 | 94 | test('simple', async () => { |
23 | 95 | const md = new MarkdownString('a'); |
24 | 96 | const result = store.add(testRenderer.render(md)); |
|
0 commit comments