Change:
Type: <bBug</bCurrently the formatting of the test report popup is broken. The line does not start on the left, but in the middle. <img width="1311" alt="CleanShot 2023-06-21 at 16 40 01 src=""VS Code version: Code - Insiders 1.80.0-insider (, 2023-06-21T08:21:53.800Z) OS version: Darwin arm64 22.5.0 Modes: <details<summarySystem Info</summary </details<details<summaryExtensions (81)</summaryExtensionVersion ------ better-comments3.0.2 codesnap1.3.4 vscode-m3u1.0.0 TabOut0.2.2 Bookmarks13.3.1 project-manager12.7.0 flutter-snippets3.0.0 toggle-excluded-files2.0.0 clipboard-history1.0.7 melos-code1.0.1 vscode-intelephense-client1.9.5 ccs-flutter-color1.0.2 gitignore0.9.0 vscode-mysql-client26.5.5 vscode-mac-color-picker1.1.0 markdown-table-prettify3.6.0 dart-code3.67. flutter3.67. vscode-markdownlint0.51.0 githistory0.6.20 gitlens14.0.1 bloc6.6.3 mason0.1.10 flutter-coverage0.0.4 shell-format7.2.5 copilot-chat0.3. copilot-labs0.14.884 copilot-nightly1.90.175 vscode-github-actions0.25.8 vscode-pull-request-github0.66.1 gitlab-workflow3.67.0 todo-tree0.0.226 terraform2.26.1 json-to-dart3.5.8 rest-client0.25.1 monkey-patch0.1.23 pubspec-assist2.3.2 shortcut-menu-bar3.0.4 intellij-idea-keybindings1.5.9 indent-to-bracket1.2.2 flutter-intl1.19.2 syntax-project-pbxproj0.1.3 flutter-snippets1.0.5 Kotlin1.7.1 rainbow-csv3.7.0 git-graph1.30.0 dart-barrel-file-generator6.1.1 diff-merge0.7.0 vscode-docker1.25.1 vscode-language-pack-de1.80. remote-containers0.296.0 live-server0.4.8 vsliveshare1.0.5873 vsliveshare-pack0.4.0 vscode-flutter-freezed-helper0.1.0 sqltools0.27.1 sqltools-driver-mysql0.5.1 awesome-flutter-snippets4.0.1 vscode-streamdeck4.1.6 vscode-yaml-sort6.5.3 vscode-versionlens1.5.0 material-icon-theme4.28.0 remove-comments1.2.2 vscode-thunder-client2.7.7 vscode-yaml1.13.0 vscode-sort-json1.20.0 command-variable1.53.0 flutter-riverpod-snippets1.2.2 vscode-coverage-gutters2.11.0 lcov-lang1.0.1 swift-lang1.3.0 freezed-model-gen1.0.1 better-search0.0.6 errorlens3.11.1 vscode-lldb1.9.2 vscode-wakatime24.0.14 vscode-todo-highlight1.0.5 php-debug1.32.1 vscode-surround1.5.0 markdown-all-in-one3.5.1 material-theme3.15.17 (3 theme extensions excluded) </details<details<summaryA/B Experiments</summary</details<!-- generated by issue reporter --
This issue is caused by an extension, please file it with the repository (or contact) the extension has linked in its overview in VS Code or the for VS Code. See also our . If you don't know which extension is causing the problem, you can run from the command palette (F1) to help identify the problem extension. Happy Coding!
this only broke in Insiders - is it a deliberate change? ! The issue is that without in doesn't render as expected: This was already the case for TestRun.appendOutput(), but not previously here. Is it possible to call this out in the docs, because I think it's confusing that an API that has nothing to do with the terminal behaves in this way without being documented.
ah, I see. Yea for that case we should not do this.
out of interest, why is it bad to do here, but ok for ? It's always felt odd to me to have terminal semantics in APIs that are not clearly terminal-based
appendOutput is meant to take subprocess "terminal" output from the test runner. As of this iteration we now use a for showing this output, so colors and other escape sequences work as expected.
Understood. Although the asymmetry here feels a little awkward. I wonder if I'm using the APIs as expected (especially since I get a lot of complaints about this Peek Error window, and requests to turn it off - but it's not a language-specific setting). I don't currently have structured failure info from my test runner, so we often end up sending the same failure message to both and . Maybe it would help if the docs gave some guidance on what data should be sent where? For the Dart runner, we don't get any failure text at the end when a test fails, we just get events that produce error output, and then a "test failed" status. IIRC, we can't call without a message, so I think we end up duplicating the last error event sent to into it. It all feels a bit messy.
Perhaps a simple API solution to that would be to return a TestMessage from , such that if that message is reused to indicate failure the editor can deduplicate that. What do you think?
Yes, I like that idea :-)
<!-- AUTHORVERIFICATIONREQUEST --This bug has been fixed in the latest release of ! you can help us out by commenting if things are now working as expected. If things still don't seem right, please ensure you're on version of Insiders (today's or later - you can use in the command palette to check), and leave a comment letting us know what isn't working as expected. Happy Coding!
Verification steps: Start with the test provider sample Change this line to be something like F5, and run a test that fails. Verify the test message is formatted correctly like
should I file an issue about the change described above (to avoid duplication of the last output event and the failure event), or did you already open one?

Discussion:
import { ICodeEditor, IDiffEditorConstructionOptions, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; <ins> import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; </ins> import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { IDiffEditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions';

this.contentProviders = [ this._register(this.instantiationService.createInstance(DiffContentProvider, this.editor, messageContainer)), this._register(this.instantiationService.createInstance(MarkdownTestMessagePeek, messageContainer)), <del> this._register(this.instantiationService.createInstance(PlainTextMessagePeek, messageContainer, isInPeekView)), </del> <ins> this._register(this.instantiationService.createInstance(TerminalMessagePeek, messageContainer, isInPeekView)), this._register(this.instantiationService.createInstance(PlainTextMessagePeek, this.editor, messageContainer)), </ins> ]; const treeContainer = dom.append(containerElement, dom.$('.test-output-peek-tree'));

} } class PlainTextMessagePeek extends Disposable implements IPeekOutputRenderer { <ins> private readonly widget = this._register(new MutableDisposable<CodeEditorWidget>()); private readonly model = this._register(new MutableDisposable()); private dimension?: dom.IDimension; constructor( private readonly editor: ICodeEditor | undefined, private readonly container: HTMLElement, @IInstantiationService private readonly instantiationService: IInstantiationService, @ITextModelService private readonly modelService: ITextModelService, ) { super(); } public async update(subject: InspectSubject) { if (!(subject instanceof MessageSubject)) { return this.clear(); } const message = subject.messages[subject.messageIndex]; if (isDiffable(message) || typeof message.message !== 'string') { return this.clear(); } const modelRef = this.model.value = await this.modelService.createModelReference(subject.messageUri); if (!this.widget.value) { this.widget.value = this.editor ? this.instantiationService.createInstance( EmbeddedCodeEditorWidget, this.container, commonEditorOptions, {}, this.editor, ) : this.instantiationService.createInstance( CodeEditorWidget, this.container, commonEditorOptions, { isSimpleWidget: true } ); if (this.dimension) { this.widget.value.layout(this.dimension); } } this.widget.value.setModel(modelRef.object.textEditorModel); this.widget.value.updateOptions(commonEditorOptions); } private clear() { this.model.clear(); this.widget.clear(); } public layout(dimensions: dom.IDimension) { this.dimension = dimensions; this.widget.value?.layout(dimensions); } } class TerminalMessagePeek extends Disposable implements IPeekOutputRenderer { </ins> private dimensions?: dom.IDimension; private readonly terminalCwd = this._register(new MutableObservableValue<string>(''));

public async update(subject: InspectSubject) { this.outputDataListener.clear(); <ins> if (!(subject instanceof TaskSubject)) { return this.clear(); } </ins> <del> if (subject instanceof MessageSubject) { const message = subject.messages[subject.messageIndex]; if (isDiffable(message) || typeof message.message !== 'string') { return this.clear(); } this.updateCwd(subject.test.uri); const terminal = await this.makeTerminal(); terminal.write(message.message); this.layoutTerminal(terminal); this.attachTerminalToDom(terminal); } else { const result = this.resultService.getResult(subject.resultId); const task = result?.tasks[subject.taskIndex]; if (!task) { return this.clear(); } </del> <ins> const result = this.resultService.getResult(subject.resultId); const task = result?.tasks[subject.taskIndex]; if (!task) { return this.clear(); } </ins> <del> // Update the cwd and use the first test to try to hint at the correct cwd, // but often this will fall back to the first workspace folder. this.updateCwd(Iterable.find(result.tests, t => !!t.item.uri)?.item.uri); </del> <ins> // Update the cwd and use the first test to try to hint at the correct cwd, // but often this will fall back to the first workspace folder. this.updateCwd(Iterable.find(result.tests, t => !!t.item.uri)?.item.uri); </ins> <del> const terminal = await this.makeTerminal(); if (result instanceof LiveTestResult) { let hadData = false; for (const buffer of task.output.buffers) { hadData ||= buffer.byteLength > 0; terminal.write(buffer.buffer); } if (!hadData && !task.running) { this.writeNotice(terminal, localize('runNoOutout', 'The test run did not record any output.')); } } else { this.writeNotice(terminal, localize('runNoOutputForPast', 'Test output is only available for new test runs.')); </del> <ins> const terminal = await this.makeTerminal(); if (result instanceof LiveTestResult) { let hadData = false; for (const buffer of task.output.buffers) { hadData ||= buffer.byteLength > 0; terminal.write(buffer.buffer); </ins> } <del> this.attachTerminalToDom(terminal); this.outputDataListener.value = task.output.onDidWriteData(e => terminal.write(e.buffer)); </del> <ins> if (!hadData && !task.running) { this.writeNotice(terminal, localize('runNoOutout', 'The test run did not record any output.')); } } else { this.writeNotice(terminal, localize('runNoOutputForPast', 'Test output is only available for new test runs.')); </ins> } <ins> this.attachTerminalToDom(terminal); this.outputDataListener.value = task.output.onDidWriteData(e => terminal.write(e.buffer)); </ins> } private updateCwd(testUri?: URI) {