From 43cf7099c064e4926407a26b9f7fa9a1564c9e24 Mon Sep 17 00:00:00 2001 From: "Michael (SPG) Weng" Date: Tue, 19 Nov 2024 14:19:19 -0500 Subject: [PATCH 1/8] Added first set of integration tests - Real time edit for swift file - Initial renders of different types of tutorial file - Initial renders of different types of markdown file --- .vscode-test.js | 5 +- assets/test/.vscode/settings.json | 1 + src/documentation/DocumentationManager.ts | 7 + .../DocumentationPreviewEditor.ts | 21 +- .../DocumentationManager.test.ts | 70 ------ .../DocumentationPreview.test.ts | 225 ++++++++++++++++++ 6 files changed, 251 insertions(+), 78 deletions(-) delete mode 100644 test/integration-tests/documentation/DocumentationManager.test.ts create mode 100644 test/integration-tests/documentation/DocumentationPreview.test.ts diff --git a/.vscode-test.js b/.vscode-test.js index 43e2dcc09..7e254d35e 100644 --- a/.vscode-test.js +++ b/.vscode-test.js @@ -42,8 +42,9 @@ module.exports = defineConfig({ color: true, timeout, forbidOnly: isCIBuild, - grep: isFastTestRun ? "@slow" : undefined, - invert: isFastTestRun, + // grep: isFastTestRun ? "@slow" : undefined, + // invert: isFastTestRun, + grep: "Documentation Preview", slow: 10000, reporter: path.join(__dirname, ".mocha-reporter.js"), reporterOptions: { diff --git a/assets/test/.vscode/settings.json b/assets/test/.vscode/settings.json index 211bc89c9..af1c095e3 100644 --- a/assets/test/.vscode/settings.json +++ b/assets/test/.vscode/settings.json @@ -2,6 +2,7 @@ "swift.disableAutoResolve": true, "swift.autoGenerateLaunchConfigurations": false, "swift.debugger.useDebugAdapterFromToolchain": true, + "swift.sourcekit-lsp.serverPath": "/Users/michaelweng/dt/sourcekit-lsp/.build/debug/sourcekit-lsp", "swift.additionalTestArguments": [ "-Xswiftc", "-DTEST_ARGUMENT_SET_VIA_TEST_BUILD_ARGUMENTS_SETTING" diff --git a/src/documentation/DocumentationManager.ts b/src/documentation/DocumentationManager.ts index 920a5923e..81b24b32d 100644 --- a/src/documentation/DocumentationManager.ts +++ b/src/documentation/DocumentationManager.ts @@ -21,6 +21,7 @@ import contextKeys from "../contextKeys"; export class DocumentationManager implements vscode.Disposable { private previewEditor?: DocumentationPreviewEditor; private editorUpdatedContentEmitter = new vscode.EventEmitter(); + private editorRenderedEmitter = new vscode.EventEmitter(); constructor( private readonly extension: vscode.ExtensionContext, @@ -28,6 +29,7 @@ export class DocumentationManager implements vscode.Disposable { ) {} onPreviewDidUpdateContent = this.editorUpdatedContentEmitter.event; + onPreviewDidRenderContent = this.editorRenderedEmitter.event; async launchDocumentationPreview(): Promise { if (!contextKeys.supportsDocumentationRendering) { @@ -43,8 +45,13 @@ export class DocumentationManager implements vscode.Disposable { this.previewEditor = new DocumentationPreviewEditor(this.extension, this.context); const subscriptions: vscode.Disposable[] = [ this.previewEditor.onDidUpdateContent(content => { + // console.log(`message content:\n${JSON.stringify(content, null, 2)}`); this.editorUpdatedContentEmitter.fire(content); }), + this.previewEditor.onDidRenderContent(() => { + console.log(`rendered.`); + this.editorRenderedEmitter.fire(); + }), this.previewEditor.onDidDispose(() => { subscriptions.forEach(d => d.dispose()); this.previewEditor = undefined; diff --git a/src/documentation/DocumentationPreviewEditor.ts b/src/documentation/DocumentationPreviewEditor.ts index 350678a67..a1e07f6ed 100644 --- a/src/documentation/DocumentationPreviewEditor.ts +++ b/src/documentation/DocumentationPreviewEditor.ts @@ -24,6 +24,7 @@ export class DocumentationPreviewEditor implements vscode.Disposable { private subscriptions: vscode.Disposable[] = []; private disposeEmitter = new vscode.EventEmitter(); + private renderEmitter = new vscode.EventEmitter(); private updateContentEmitter = new vscode.EventEmitter(); constructor( @@ -59,12 +60,14 @@ export class DocumentationPreviewEditor implements vscode.Disposable { this.webviewPanel.webview.html = documentationHTML; this.subscriptions.push( this.webviewPanel.webview.onDidReceiveMessage(this.receiveMessage.bind(this)), - vscode.window.onDidChangeActiveTextEditor(editor => - this.renderDocumentation(editor) - ), - vscode.window.onDidChangeTextEditorSelection(event => - this.renderDocumentation(event.textEditor) - ), + vscode.window.onDidChangeActiveTextEditor(editor => { + console.log(`editor changed`); + this.renderDocumentation(editor); + }), + vscode.window.onDidChangeTextEditorSelection(event => { + console.log(`change kind:{${event.kind}}`); + this.renderDocumentation(event.textEditor); + }), this.webviewPanel.onDidDispose(this.dispose.bind(this)) ); // Reveal the editor, but don't change the focus of the active text editor @@ -79,6 +82,9 @@ export class DocumentationPreviewEditor implements vscode.Disposable { /** An event that is fired when the Documentation Preview Editor updates its content */ onDidUpdateContent = this.updateContentEmitter.event; + /** An event that is fired when the Documentation Preview Editor renders its content */ + onDidRenderContent = this.renderEmitter.event; + reveal() { this.webviewPanel.reveal(); } @@ -102,6 +108,9 @@ export class DocumentationPreviewEditor implements vscode.Disposable { case "loaded": this.renderDocumentation(vscode.window.activeTextEditor); break; + case "rendered": + this.renderEmitter.fire(); + break; } } diff --git a/test/integration-tests/documentation/DocumentationManager.test.ts b/test/integration-tests/documentation/DocumentationManager.test.ts deleted file mode 100644 index a7b276a89..000000000 --- a/test/integration-tests/documentation/DocumentationManager.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the VS Code Swift open source project -// -// Copyright (c) 2024 the VS Code Swift project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of VS Code Swift project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import * as vscode from "vscode"; -import { expect } from "chai"; -import { folderContextPromise, globalWorkspaceContextPromise } from "../extension.test"; -import { waitForNoRunningTasks } from "../../utilities"; -import { testAssetUri } from "../../fixtures"; -import { FolderContext } from "../../../src/FolderContext"; -import { WorkspaceContext } from "../../../src/WorkspaceContext"; -import { Commands } from "../../../src/commands"; -import { Workbench } from "../../../src/utilities/commands"; -import { RenderNode } from "../../../src/documentation/webview/WebviewMessage"; -import contextKeys from "../../../src/contextKeys"; - -suite("Documentation Preview Editor", function () { - this.timeout(5000); // Tests are short, but rely on SourceKit-LSP: give 5 seconds for each one - - let folderContext: FolderContext; - let workspaceContext: WorkspaceContext; - - suiteSetup(async function () { - workspaceContext = await globalWorkspaceContextPromise; - await waitForNoRunningTasks(); - folderContext = await folderContextPromise("SlothCreatorExample"); - await workspaceContext.focusFolder(folderContext); - }); - - suiteTeardown(async () => { - await vscode.commands.executeCommand(Workbench.ACTION_CLOSEALLEDITORS); - }); - - test("renders documentation for an opened Swift file", async function () { - if (!contextKeys.supportsDocumentationRendering) { - this.skip(); - return; - } - - // Open a Swift file before we launch the documentation preview - await vscode.window.showTextDocument( - testAssetUri("SlothCreatorExample/Sources/SlothCreator/Models/Sloth.swift") - ); - - // Launch the documentation preview and wait for the content to update - await expect(vscode.commands.executeCommand(Commands.PREVIEW_DOCUMENTATION)).to.eventually - .be.true; - - // Wait for the content to be updated - const renderedContent = await waitForNextContentUpdate(workspaceContext); - const uri = vscode.Uri.parse(renderedContent.identifier.url); - expect(uri.path).to.equal("/documentation/Sloth/Sloth"); - }); -}); - -function waitForNextContentUpdate(context: WorkspaceContext): Promise { - return new Promise(resolve => { - context.documentation.onPreviewDidUpdateContent(resolve); - }); -} diff --git a/test/integration-tests/documentation/DocumentationPreview.test.ts b/test/integration-tests/documentation/DocumentationPreview.test.ts new file mode 100644 index 000000000..8886c6755 --- /dev/null +++ b/test/integration-tests/documentation/DocumentationPreview.test.ts @@ -0,0 +1,225 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2024 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import * as vscode from "vscode"; +import contextKeys from "../../../src/contextKeys"; +import { expect } from "chai"; +import { folderContextPromise, globalWorkspaceContextPromise } from "../extension.test"; +import { waitForNoRunningTasks } from "../../utilities"; +import { testAssetUri } from "../../fixtures"; +import { FolderContext } from "../../../src/FolderContext"; +import { WorkspaceContext } from "../../../src/WorkspaceContext"; +import { Commands } from "../../../src/commands"; +import { Workbench } from "../../../src/utilities/commands"; +import { RenderNode } from "../../../src/documentation/webview/WebviewMessage"; + +suite("Documentation Preview", function () { + // Tests are short, but rely on SourceKit-LSP: give 30 seconds for each one + this.timeout(30 * 1000); + //this.timeout(30 * 30 * 1000); + + let folderContext: FolderContext; + let workspaceContext: WorkspaceContext; + + suiteSetup(async function () { + workspaceContext = await globalWorkspaceContextPromise; + await waitForNoRunningTasks(); + folderContext = await folderContextPromise("SlothCreatorExample"); + await workspaceContext.focusFolder(folderContext); + }); + + suiteTeardown(async () => { + await vscode.commands.executeCommand(Workbench.ACTION_CLOSEALLEDITORS); + }); + + setup(function () { + if (!contextKeys.supportsDocumentationRendering) { + this.skip(); + } + }); + + async function initialRenderTest( + uri: string, + expectedContent: string, + editToCheck: string + ): Promise<{ editor: vscode.TextEditor; document: vscode.TextDocument }> { + // Set up content promise before file set up + const contentPromise = waitForNextContentUpdate(workspaceContext); + + // Open a Swift file before we launch the documentation preview + const swiftFileUri = testAssetUri(uri); + const initPos = new vscode.Position(0, 0); + const document = await vscode.workspace.openTextDocument(swiftFileUri); + const editor = await vscode.window.showTextDocument(document, { + selection: new vscode.Selection(initPos, initPos), + }); + + // Check if the webview panel is visible, if running in isolation the preview command has to + // be executed, otherwise we can proceed with the test steps reusing the preview panel + if (!isTabVisible("swift.previewDocumentationEditor", "Preview Swift Documentation")) { + // Launch the documentation preview and wait for render to complete + await expect(vscode.commands.executeCommand(Commands.PREVIEW_DOCUMENTATION)).to + .eventually.be.true; + } + await expect(waitForRender(workspaceContext)).to.eventually.be.true; + + // Wait for the test promise to complete + console.log("Waiting for initial content update..."); + const updatedContent = await contentPromise; + const updatedContentString = JSON.stringify(updatedContent, null, 2); + + // Assert that the content text contain the right content + expect(updatedContentString, `${updatedContentString}`).to.include(expectedContent); + expect(updatedContentString, `${updatedContentString}`).to.not.include(editToCheck); + return { editor, document }; + } + + test("renders documentation for an opened Swift file", async function () { + // Check for initial Render + const expectedEdit = "my edit: swift file"; + const { editor, document } = await initialRenderTest( + "SlothCreatorExample/Sources/SlothCreator/Models/Sloth.swift", + "A model representing a sloth.", + expectedEdit + ); + + // Set up test promise + let contentPromise = waitForNextContentUpdate(workspaceContext); + + // Edit the focused text document, appending expected edit at the end of line 3 + const line = 2; // Line 3 in zero-based index + await editor.edit(editBuilder => { + const lineEnd = document.lineAt(line).range.end; + editBuilder.insert(lineEnd, expectedEdit); + }); + + // Update the cursor position to the end of the inserted text + const newCursorPos = new vscode.Position( + line, + document.lineAt(line).range.end.character + expectedEdit.length + ); + editor.selection = new vscode.Selection(newCursorPos, newCursorPos); + + // FIXME: We are off by 1 right now... so need to do 1 more action + // FIXME: Also the above is consistent only if on cached-run (second run and onwards) + await waitForRender(workspaceContext); + console.log("Waiting for post edit content update..."); + let updatedContent = await contentPromise; + let updatedContentString = JSON.stringify(updatedContent, null, 2); + expect(updatedContentString, `${updatedContentString}`).to.not.include(expectedEdit); + + // Set up test promise, and change selection which will trigger an update + contentPromise = waitForNextContentUpdate(workspaceContext); + const initPos = new vscode.Position(0, 0); + editor.selection = new vscode.Selection(initPos, initPos); + + // Wait for render and test promise to complete + await waitForRender(workspaceContext); + console.log("Waiting for post edit content update, FIXME: 1+ action..."); + updatedContent = await contentPromise; + updatedContentString = JSON.stringify(updatedContent, null, 2); + expect(updatedContentString, `${updatedContentString}`).to.include(expectedEdit); + }); + + test("renders documentation for a tutorial overview file", async function () { + // Check for initial Render + const expectedEdit = "my edit: tutorial overview"; + const { editor, document } = await initialRenderTest( + "SlothCreatorExample/Sources/SlothCreator/SlothCreator.docc/Tutorials/SlothCreator.tutorial", + "Meet SlothCreator", + expectedEdit + ); + }); + + test("renders documentation for a single tutorial file", async function () { + // Check for initial Render + const expectedEdit = "my edit: single tutorial"; + const { editor, document } = await initialRenderTest( + "SlothCreatorExample/Sources/SlothCreator/SlothCreator.docc/Tutorials/Creating Custom Sloths.tutorial", + "Creating Custom Sloths", + expectedEdit + ); + }); + + test("renders documentation for a generic markdown file", async function () { + // Check for initial Render + const expectedEdit = "my edit: generic markdown"; + const { editor, document } = await initialRenderTest( + "SlothCreatorExample/Sources/SlothCreator/SlothCreator.docc/GettingStarted.md", + "Getting Started with Sloths", + expectedEdit + ); + }); + + test("renders documentation for a symbol linkage markdown file", async function () { + // FIXME: This is not working yet + this.skip(); + // Check for initial Render + const expectedEdit = "my edit: symbol linkage markdown"; + const { editor, document } = await initialRenderTest( + "SlothCreatorExample/Sources/SlothCreator/SlothCreator.docc/GettingStarted.md", + "Getting Started with Sloths", + expectedEdit + ); + }); + + test("renders documentation for a symbol providing markdown file", async function () { + // FIXME: This is not working yet + this.skip(); + // Check for initial Render + const expectedEdit = "my edit: symbol providing markdown"; + const { editor, document } = await initialRenderTest( + "SlothCreatorExample/Sources/SlothCreator/SlothCreator.docc/SlothCreator.md", + "Catalog sloths you find", + expectedEdit + ); + }); +}); + +function waitForNextContentUpdate(context: WorkspaceContext): Promise { + return new Promise(resolve => { + const disposable = context.documentation.onPreviewDidUpdateContent( + (renderNode: RenderNode) => { + resolve(renderNode); + disposable.dispose(); + } + ); + }); +} + +function waitForRender(context: WorkspaceContext): Promise { + return new Promise(resolve => { + const disposable = context.documentation.onPreviewDidRenderContent(() => { + resolve(true); + disposable.dispose(); + }); + }); +} + +function isTabVisible(viewType: string, title: string): boolean { + for (const group of vscode.window.tabGroups.all) { + for (const tab of group.tabs) { + // Check if the tab is of type TabInputWebview and matches the viewType and title + if ( + tab.input instanceof vscode.TabInputWebview && + tab.input.viewType.includes(viewType) && + tab.label === title + ) { + // We are not checking if tab is active, so return true as long as the if clause is true + return true; + } + } + } + return false; +} From 0d55e29976a3e4be5c80709f4543932ae3f9c630 Mon Sep 17 00:00:00 2001 From: "Michael (SPG) Weng" Date: Fri, 22 Nov 2024 17:32:39 -0500 Subject: [PATCH 2/8] Remove some local set up files --- .vscode-test.js | 5 ++--- assets/test/.vscode/settings.json | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.vscode-test.js b/.vscode-test.js index 7e254d35e..43e2dcc09 100644 --- a/.vscode-test.js +++ b/.vscode-test.js @@ -42,9 +42,8 @@ module.exports = defineConfig({ color: true, timeout, forbidOnly: isCIBuild, - // grep: isFastTestRun ? "@slow" : undefined, - // invert: isFastTestRun, - grep: "Documentation Preview", + grep: isFastTestRun ? "@slow" : undefined, + invert: isFastTestRun, slow: 10000, reporter: path.join(__dirname, ".mocha-reporter.js"), reporterOptions: { diff --git a/assets/test/.vscode/settings.json b/assets/test/.vscode/settings.json index af1c095e3..211bc89c9 100644 --- a/assets/test/.vscode/settings.json +++ b/assets/test/.vscode/settings.json @@ -2,7 +2,6 @@ "swift.disableAutoResolve": true, "swift.autoGenerateLaunchConfigurations": false, "swift.debugger.useDebugAdapterFromToolchain": true, - "swift.sourcekit-lsp.serverPath": "/Users/michaelweng/dt/sourcekit-lsp/.build/debug/sourcekit-lsp", "swift.additionalTestArguments": [ "-Xswiftc", "-DTEST_ARGUMENT_SET_VIA_TEST_BUILD_ARGUMENTS_SETTING" From 9aac9198ff330934f7c361f7246d144ace9d3fa7 Mon Sep 17 00:00:00 2001 From: "Michael (SPG) Weng" Date: Mon, 25 Nov 2024 11:34:47 -0500 Subject: [PATCH 3/8] Add edit test for all tests in the test suite --- .../DocumentationPreview.test.ts | 60 ++++++++++++++++--- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/test/integration-tests/documentation/DocumentationPreview.test.ts b/test/integration-tests/documentation/DocumentationPreview.test.ts index 8886c6755..7a0eb6ff4 100644 --- a/test/integration-tests/documentation/DocumentationPreview.test.ts +++ b/test/integration-tests/documentation/DocumentationPreview.test.ts @@ -26,8 +26,8 @@ import { RenderNode } from "../../../src/documentation/webview/WebviewMessage"; suite("Documentation Preview", function () { // Tests are short, but rely on SourceKit-LSP: give 30 seconds for each one - this.timeout(30 * 1000); - //this.timeout(30 * 30 * 1000); + // this.timeout(30 * 1000); + this.timeout(30 * 30 * 1000); let folderContext: FolderContext; let workspaceContext: WorkspaceContext; @@ -49,6 +49,35 @@ suite("Documentation Preview", function () { } }); + async function editRenderTest( + line: number, // zero-based index, for line 3 this var will be 2 + expectedEdit: string, + editor: vscode.TextEditor, + document: vscode.TextDocument + ) { + // Set up test promise + const contentPromise = waitForNextContentUpdate(workspaceContext); + + // Edit the focused text document, appending expected edit at the end of line 3 + await editor.edit(editBuilder => { + const lineEnd = document.lineAt(line).range.end; + editBuilder.insert(lineEnd, expectedEdit); + }); + + // Update the cursor position to the end of the inserted text + const newCursorPos = new vscode.Position( + line, + document.lineAt(line).range.end.character + expectedEdit.length + ); + editor.selection = new vscode.Selection(newCursorPos, newCursorPos); + + await expect(waitForRender(workspaceContext)).to.eventually.be.true; + console.log("Waiting for post edit content update..."); + const updatedContent = await contentPromise; + const updatedContentString = JSON.stringify(updatedContent, null, 2); + expect(updatedContentString, `${updatedContentString}`).to.include(expectedEdit); + } + async function initialRenderTest( uri: string, expectedContent: string, @@ -113,7 +142,7 @@ suite("Documentation Preview", function () { // FIXME: We are off by 1 right now... so need to do 1 more action // FIXME: Also the above is consistent only if on cached-run (second run and onwards) - await waitForRender(workspaceContext); + await expect(waitForRender(workspaceContext)).to.eventually.be.true; console.log("Waiting for post edit content update..."); let updatedContent = await contentPromise; let updatedContentString = JSON.stringify(updatedContent, null, 2); @@ -125,7 +154,7 @@ suite("Documentation Preview", function () { editor.selection = new vscode.Selection(initPos, initPos); // Wait for render and test promise to complete - await waitForRender(workspaceContext); + await expect(waitForRender(workspaceContext)).to.eventually.be.true; console.log("Waiting for post edit content update, FIXME: 1+ action..."); updatedContent = await contentPromise; updatedContentString = JSON.stringify(updatedContent, null, 2); @@ -140,6 +169,9 @@ suite("Documentation Preview", function () { "Meet SlothCreator", expectedEdit ); + + // Insert edit at the end of line 3 and assert for change + await editRenderTest(2, expectedEdit, editor, document); }); test("renders documentation for a single tutorial file", async function () { @@ -150,6 +182,9 @@ suite("Documentation Preview", function () { "Creating Custom Sloths", expectedEdit ); + + // Insert edit at the end of line 3 and assert for change + await editRenderTest(2, expectedEdit, editor, document); }); test("renders documentation for a generic markdown file", async function () { @@ -160,6 +195,9 @@ suite("Documentation Preview", function () { "Getting Started with Sloths", expectedEdit ); + + // Insert edit at the end of line 3 and assert for change + await editRenderTest(2, expectedEdit, editor, document); }); test("renders documentation for a symbol linkage markdown file", async function () { @@ -168,10 +206,13 @@ suite("Documentation Preview", function () { // Check for initial Render const expectedEdit = "my edit: symbol linkage markdown"; const { editor, document } = await initialRenderTest( - "SlothCreatorExample/Sources/SlothCreator/SlothCreator.docc/GettingStarted.md", - "Getting Started with Sloths", + "SlothCreatorExample/Sources/SlothCreator/SlothCreator.docc/SlothCreator.md", + "Catalog sloths you find", expectedEdit ); + + // Insert edit at the end of line 3 and assert for change + await editRenderTest(2, expectedEdit, editor, document); }); test("renders documentation for a symbol providing markdown file", async function () { @@ -180,10 +221,13 @@ suite("Documentation Preview", function () { // Check for initial Render const expectedEdit = "my edit: symbol providing markdown"; const { editor, document } = await initialRenderTest( - "SlothCreatorExample/Sources/SlothCreator/SlothCreator.docc/SlothCreator.md", - "Catalog sloths you find", + "SlothCreatorExample/Sources/SlothCreator/SlothCreator.docc/Extensions/Sloth.md", + "Creating a Sloth", expectedEdit ); + + // Insert edit at the end of line 3 and assert for change + await editRenderTest(2, expectedEdit, editor, document); }); }); From cd5a89ff732c1f37c35752afa7df2cdefbec79dd Mon Sep 17 00:00:00 2001 From: "Michael (SPG) Weng" Date: Mon, 25 Nov 2024 17:29:44 -0500 Subject: [PATCH 4/8] Add focus switch tests --- .../DocumentationPreviewEditor.ts | 9 +- src/utilities/commands.ts | 1 + .../DocumentationPreview.test.ts | 191 +++++++++++++----- 3 files changed, 146 insertions(+), 55 deletions(-) diff --git a/src/documentation/DocumentationPreviewEditor.ts b/src/documentation/DocumentationPreviewEditor.ts index a1e07f6ed..09215bcbd 100644 --- a/src/documentation/DocumentationPreviewEditor.ts +++ b/src/documentation/DocumentationPreviewEditor.ts @@ -19,6 +19,11 @@ import { RenderNode, WebviewMessage } from "./webview/WebviewMessage"; import { WorkspaceContext } from "../WorkspaceContext"; import { RenderDocumentationRequest } from "../sourcekit-lsp/extensions/RenderDocumentationRequest"; +export enum PreviewEditorConstant { + VIEW_TYPE = "swift.previewDocumentationEditor", + TITLE = "Preview Swift Documentation", +} + export class DocumentationPreviewEditor implements vscode.Disposable { private readonly webviewPanel: vscode.WebviewPanel; private subscriptions: vscode.Disposable[] = []; @@ -34,8 +39,8 @@ export class DocumentationPreviewEditor implements vscode.Disposable { const swiftDoccRenderPath = this.extension.asAbsolutePath("assets/swift-docc-render"); // Create and hook up events for the WebviewPanel this.webviewPanel = vscode.window.createWebviewPanel( - "swift.previewDocumentationEditor", - "Preview Swift Documentation", + PreviewEditorConstant.VIEW_TYPE, + PreviewEditorConstant.TITLE, { viewColumn: vscode.ViewColumn.Beside, preserveFocus: true }, { enableScripts: true, diff --git a/src/utilities/commands.ts b/src/utilities/commands.ts index cc994f76c..e8a6cf2b4 100644 --- a/src/utilities/commands.ts +++ b/src/utilities/commands.ts @@ -16,4 +16,5 @@ export enum Workbench { ACTION_DEBUG_CONTINUE = "workbench.action.debug.continue", ACTION_CLOSEALLEDITORS = "workbench.action.closeAllEditors", ACTION_RELOADWINDOW = "workbench.action.reloadWindow", + ACTION_PREVIOUSEDITORINGROUP = "workbench.action.previousEditorInGroup", } diff --git a/test/integration-tests/documentation/DocumentationPreview.test.ts b/test/integration-tests/documentation/DocumentationPreview.test.ts index 7a0eb6ff4..a469aa26a 100644 --- a/test/integration-tests/documentation/DocumentationPreview.test.ts +++ b/test/integration-tests/documentation/DocumentationPreview.test.ts @@ -23,11 +23,11 @@ import { WorkspaceContext } from "../../../src/WorkspaceContext"; import { Commands } from "../../../src/commands"; import { Workbench } from "../../../src/utilities/commands"; import { RenderNode } from "../../../src/documentation/webview/WebviewMessage"; +import { PreviewEditorConstant } from "../../../src/documentation/DocumentationPreviewEditor"; suite("Documentation Preview", function () { // Tests are short, but rely on SourceKit-LSP: give 30 seconds for each one - // this.timeout(30 * 1000); - this.timeout(30 * 30 * 1000); + this.timeout(30 * 1000); let folderContext: FolderContext; let workspaceContext: WorkspaceContext; @@ -50,24 +50,20 @@ suite("Documentation Preview", function () { }); async function editRenderTest( - line: number, // zero-based index, for line 3 this var will be 2 + position: vscode.Position, expectedEdit: string, - editor: vscode.TextEditor, - document: vscode.TextDocument + editor: vscode.TextEditor ) { // Set up test promise const contentPromise = waitForNextContentUpdate(workspaceContext); - // Edit the focused text document, appending expected edit at the end of line 3 - await editor.edit(editBuilder => { - const lineEnd = document.lineAt(line).range.end; - editBuilder.insert(lineEnd, expectedEdit); - }); + // Edit the focused text document, appending expected edit at the end of provided position + await editor.edit(editBuilder => editBuilder.insert(position, expectedEdit)); // Update the cursor position to the end of the inserted text const newCursorPos = new vscode.Position( - line, - document.lineAt(line).range.end.character + expectedEdit.length + position.line, + position.character + expectedEdit.length ); editor.selection = new vscode.Selection(newCursorPos, newCursorPos); @@ -82,21 +78,20 @@ suite("Documentation Preview", function () { uri: string, expectedContent: string, editToCheck: string - ): Promise<{ editor: vscode.TextEditor; document: vscode.TextDocument }> { + ): Promise { // Set up content promise before file set up const contentPromise = waitForNextContentUpdate(workspaceContext); // Open a Swift file before we launch the documentation preview const swiftFileUri = testAssetUri(uri); const initPos = new vscode.Position(0, 0); - const document = await vscode.workspace.openTextDocument(swiftFileUri); - const editor = await vscode.window.showTextDocument(document, { + const editor = await vscode.window.showTextDocument(swiftFileUri, { selection: new vscode.Selection(initPos, initPos), }); // Check if the webview panel is visible, if running in isolation the preview command has to // be executed, otherwise we can proceed with the test steps reusing the preview panel - if (!isTabVisible("swift.previewDocumentationEditor", "Preview Swift Documentation")) { + if (!findTab(PreviewEditorConstant.VIEW_TYPE, PreviewEditorConstant.TITLE)) { // Launch the documentation preview and wait for render to complete await expect(vscode.commands.executeCommand(Commands.PREVIEW_DOCUMENTATION)).to .eventually.be.true; @@ -111,13 +106,13 @@ suite("Documentation Preview", function () { // Assert that the content text contain the right content expect(updatedContentString, `${updatedContentString}`).to.include(expectedContent); expect(updatedContentString, `${updatedContentString}`).to.not.include(editToCheck); - return { editor, document }; + return editor; } - test("renders documentation for an opened Swift file", async function () { + test("renders documentation for an opened Swift file + edit rendering", async function () { // Check for initial Render const expectedEdit = "my edit: swift file"; - const { editor, document } = await initialRenderTest( + const editor = await initialRenderTest( "SlothCreatorExample/Sources/SlothCreator/Models/Sloth.swift", "A model representing a sloth.", expectedEdit @@ -125,23 +120,20 @@ suite("Documentation Preview", function () { // Set up test promise let contentPromise = waitForNextContentUpdate(workspaceContext); + const insertPos = new vscode.Position(2, 32); - // Edit the focused text document, appending expected edit at the end of line 3 - const line = 2; // Line 3 in zero-based index - await editor.edit(editBuilder => { - const lineEnd = document.lineAt(line).range.end; - editBuilder.insert(lineEnd, expectedEdit); - }); + // Edit the focused text document, appending expected edit at the end of position + await editor.edit(editBuilder => editBuilder.insert(insertPos, expectedEdit)); // Update the cursor position to the end of the inserted text const newCursorPos = new vscode.Position( - line, - document.lineAt(line).range.end.character + expectedEdit.length + insertPos.line, + insertPos.character + expectedEdit.length ); editor.selection = new vscode.Selection(newCursorPos, newCursorPos); // FIXME: We are off by 1 right now... so need to do 1 more action - // FIXME: Also the above is consistent only if on cached-run (second run and onwards) + // FIXME: Also the off by 1 behaviour is consistent only if on cached-run (second run and onwards) await expect(waitForRender(workspaceContext)).to.eventually.be.true; console.log("Waiting for post edit content update..."); let updatedContent = await contentPromise; @@ -161,73 +153,166 @@ suite("Documentation Preview", function () { expect(updatedContentString, `${updatedContentString}`).to.include(expectedEdit); }); - test("renders documentation for a tutorial overview file", async function () { + test("Cursor switch: Opened Swift file, documentation to symbol, symbol edit rendering", async function () { + // Check for initial Render + const expectedSymbol = "comfortLevel"; + const editor = await initialRenderTest( + "SlothCreatorExample/Sources/SlothCreator/Models/Habitat.swift", + "The habitat where sloths live.", + expectedSymbol + ); + + // Set up test promise, and change to a location of a symbol: comfortLevel + const contentPromise = waitForNextContentUpdate(workspaceContext); + const symbolPos = new vscode.Position(25, 15); + editor.selection = new vscode.Selection(symbolPos, symbolPos); + + // Wait for render and test promise to complete + await expect(waitForRender(workspaceContext)).to.eventually.be.true; + console.log("Waiting for post cursor change content update..."); + const updatedContent = await contentPromise; + const updatedContentString = JSON.stringify(updatedContent, null, 2); + expect(updatedContentString, `${updatedContentString}`).to.include(expectedSymbol); + + // Insert edit at the desired position and assert for change: comfortLevel symbol + await editRenderTest(new vscode.Position(25, 27), "Atlantis", editor); + }); + + test("renders documentation for a tutorial overview file + edit rendering", async function () { // Check for initial Render const expectedEdit = "my edit: tutorial overview"; - const { editor, document } = await initialRenderTest( + const editor = await initialRenderTest( "SlothCreatorExample/Sources/SlothCreator/SlothCreator.docc/Tutorials/SlothCreator.tutorial", "Meet SlothCreator", expectedEdit ); - // Insert edit at the end of line 3 and assert for change - await editRenderTest(2, expectedEdit, editor, document); + // Insert edit at the desired position and assert for change + await editRenderTest(new vscode.Position(2, 128), expectedEdit, editor); }); - test("renders documentation for a single tutorial file", async function () { + test("renders documentation for a single tutorial file + edit rendering", async function () { // Check for initial Render const expectedEdit = "my edit: single tutorial"; - const { editor, document } = await initialRenderTest( + const editor = await initialRenderTest( "SlothCreatorExample/Sources/SlothCreator/SlothCreator.docc/Tutorials/Creating Custom Sloths.tutorial", "Creating Custom Sloths", expectedEdit ); - // Insert edit at the end of line 3 and assert for change - await editRenderTest(2, expectedEdit, editor, document); + // Insert edit at the desired position and assert for change + await editRenderTest(new vscode.Position(2, 109), expectedEdit, editor); }); - test("renders documentation for a generic markdown file", async function () { + test("renders documentation for a generic markdown file + edit rendering", async function () { // Check for initial Render const expectedEdit = "my edit: generic markdown"; - const { editor, document } = await initialRenderTest( + const editor = await initialRenderTest( "SlothCreatorExample/Sources/SlothCreator/SlothCreator.docc/GettingStarted.md", "Getting Started with Sloths", expectedEdit ); - // Insert edit at the end of line 3 and assert for change - await editRenderTest(2, expectedEdit, editor, document); + // Insert edit at the desired position and assert for change + await editRenderTest(new vscode.Position(2, 25), expectedEdit, editor); }); - test("renders documentation for a symbol linkage markdown file", async function () { - // FIXME: This is not working yet + test("renders documentation for a symbol linkage markdown file + edit rendering", async function () { + // FIXME: This feature is not implemented yet this.skip(); // Check for initial Render const expectedEdit = "my edit: symbol linkage markdown"; - const { editor, document } = await initialRenderTest( + const editor = await initialRenderTest( "SlothCreatorExample/Sources/SlothCreator/SlothCreator.docc/SlothCreator.md", "Catalog sloths you find", expectedEdit ); - // Insert edit at the end of line 3 and assert for change - await editRenderTest(2, expectedEdit, editor, document); + // Insert edit at the desired position and assert for change + await editRenderTest(new vscode.Position(2, 33), expectedEdit, editor); }); - test("renders documentation for a symbol providing markdown file", async function () { - // FIXME: This is not working yet + test("renders documentation for a symbol providing markdown file + edit rendering", async function () { + // FIXME: This feature is not implemented yet this.skip(); // Check for initial Render const expectedEdit = "my edit: symbol providing markdown"; - const { editor, document } = await initialRenderTest( + const editor = await initialRenderTest( "SlothCreatorExample/Sources/SlothCreator/SlothCreator.docc/Extensions/Sloth.md", "Creating a Sloth", expectedEdit ); - // Insert edit at the end of line 3 and assert for change - await editRenderTest(2, expectedEdit, editor, document); + // Insert edit at the desired position and assert for change + await editRenderTest(new vscode.Position(4, 14), expectedEdit, editor); + }); + + test("Focus switch: visible tab", async function () { + // Check for initial Render + const contentInTab2 = "Creating Custom Sloths"; + const expectedContent = "Meet SlothCreator"; + await initialRenderTest( + "SlothCreatorExample/Sources/SlothCreator/SlothCreator.docc/Tutorials/SlothCreator.tutorial", + expectedContent, + contentInTab2 + ); + + // Open tab 2 in the same tab group as the webview renderer + const webviewTabNullable = findTab( + PreviewEditorConstant.VIEW_TYPE, + PreviewEditorConstant.TITLE + ); + expect(webviewTabNullable).to.not.be.undefined; + const webviewTab = webviewTabNullable!; + const newTutorialUri = testAssetUri( + "SlothCreatorExample/Sources/SlothCreator/SlothCreator.docc/Tutorials/Creating Custom Sloths.tutorial" + ); + await vscode.window.showTextDocument(newTutorialUri, { + viewColumn: webviewTab.group.viewColumn, + }); + + // Set up test promise, and swap back to the previous editor (webview panel) + const contentPromise = waitForNextContentUpdate(workspaceContext); + await vscode.commands.executeCommand(Workbench.ACTION_PREVIOUSEDITORINGROUP); + + // Wait for render and assert webview panel retains render of last focused editor when the panel is visible + await expect(waitForRender(workspaceContext)).to.eventually.be.true; + console.log("Waiting for post visible tab change content update..."); + const updatedContent = await contentPromise; + const updatedContentString = JSON.stringify(updatedContent, null, 2); + // FIXME: This feature is not implemented yet + expect(updatedContentString, `${updatedContentString}`).to.include(expectedContent); + }); + + test("Focus switch: Swift extension", async function () { + // FIXME: This feature is not implemented yet + this.skip(); + // Check for initial Render + const extensionContent = "Food that a sloth can consume"; + await initialRenderTest( + "SlothCreatorExample/Sources/SlothCreator/Models/Sloth.swift", + "A model representing a sloth.", + extensionContent + ); + + // Set up test promise, and open an extension Swift file + const contentPromise = waitForNextContentUpdate(workspaceContext); + const extensionUri = testAssetUri( + "SlothCreatorExample/Sources/SlothCreator/Models/Food.swift" + ); + const initPos = new vscode.Position(0, 0); + await vscode.window.showTextDocument(extensionUri, { + selection: new vscode.Selection(initPos, initPos), + }); + + // Wait for render and assert webview panel to displayed that no documentation is available + await expect(waitForRender(workspaceContext)).to.eventually.be.true; + console.log("Waiting for post extension editor change content update..."); + const updatedContent = await contentPromise; + const updatedContentString = JSON.stringify(updatedContent, null, 2); + expect(updatedContentString, `${updatedContentString}`).to.include( + "Documentation is not available." + ); }); }); @@ -251,7 +336,7 @@ function waitForRender(context: WorkspaceContext): Promise { }); } -function isTabVisible(viewType: string, title: string): boolean { +function findTab(viewType: string, title: string): vscode.Tab | undefined { for (const group of vscode.window.tabGroups.all) { for (const tab of group.tabs) { // Check if the tab is of type TabInputWebview and matches the viewType and title @@ -261,9 +346,9 @@ function isTabVisible(viewType: string, title: string): boolean { tab.label === title ) { // We are not checking if tab is active, so return true as long as the if clause is true - return true; + return tab; } } } - return false; + return undefined; } From 9fe9104f3862c2761d9101684fef8032f17995f7 Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Wed, 27 Nov 2024 11:14:10 -0500 Subject: [PATCH 5/8] remove commented code --- src/documentation/DocumentationManager.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/documentation/DocumentationManager.ts b/src/documentation/DocumentationManager.ts index 81b24b32d..7304233a4 100644 --- a/src/documentation/DocumentationManager.ts +++ b/src/documentation/DocumentationManager.ts @@ -45,7 +45,6 @@ export class DocumentationManager implements vscode.Disposable { this.previewEditor = new DocumentationPreviewEditor(this.extension, this.context); const subscriptions: vscode.Disposable[] = [ this.previewEditor.onDidUpdateContent(content => { - // console.log(`message content:\n${JSON.stringify(content, null, 2)}`); this.editorUpdatedContentEmitter.fire(content); }), this.previewEditor.onDidRenderContent(() => { From 2d4402ce46ebc312064e7476a0ddb9b55d9bae9c Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Wed, 27 Nov 2024 11:14:45 -0500 Subject: [PATCH 6/8] remove console log statement --- src/documentation/DocumentationManager.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/documentation/DocumentationManager.ts b/src/documentation/DocumentationManager.ts index 7304233a4..ab95f90f8 100644 --- a/src/documentation/DocumentationManager.ts +++ b/src/documentation/DocumentationManager.ts @@ -48,7 +48,6 @@ export class DocumentationManager implements vscode.Disposable { this.editorUpdatedContentEmitter.fire(content); }), this.previewEditor.onDidRenderContent(() => { - console.log(`rendered.`); this.editorRenderedEmitter.fire(); }), this.previewEditor.onDidDispose(() => { From 64c38131f84f3ee8d00612f33280c836112ea4f1 Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Wed, 27 Nov 2024 11:15:32 -0500 Subject: [PATCH 7/8] remove more console.log statements --- src/documentation/DocumentationPreviewEditor.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/documentation/DocumentationPreviewEditor.ts b/src/documentation/DocumentationPreviewEditor.ts index 09215bcbd..83d51314a 100644 --- a/src/documentation/DocumentationPreviewEditor.ts +++ b/src/documentation/DocumentationPreviewEditor.ts @@ -66,11 +66,9 @@ export class DocumentationPreviewEditor implements vscode.Disposable { this.subscriptions.push( this.webviewPanel.webview.onDidReceiveMessage(this.receiveMessage.bind(this)), vscode.window.onDidChangeActiveTextEditor(editor => { - console.log(`editor changed`); this.renderDocumentation(editor); }), vscode.window.onDidChangeTextEditorSelection(event => { - console.log(`change kind:{${event.kind}}`); this.renderDocumentation(event.textEditor); }), this.webviewPanel.onDidDispose(this.dispose.bind(this)) From dddc7c4131bc16d7768f3d8ca446a256c100b0f2 Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Wed, 27 Nov 2024 11:16:46 -0500 Subject: [PATCH 8/8] remove even more console.log statements --- .../documentation/DocumentationPreview.test.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/test/integration-tests/documentation/DocumentationPreview.test.ts b/test/integration-tests/documentation/DocumentationPreview.test.ts index a469aa26a..64e7d083f 100644 --- a/test/integration-tests/documentation/DocumentationPreview.test.ts +++ b/test/integration-tests/documentation/DocumentationPreview.test.ts @@ -68,7 +68,6 @@ suite("Documentation Preview", function () { editor.selection = new vscode.Selection(newCursorPos, newCursorPos); await expect(waitForRender(workspaceContext)).to.eventually.be.true; - console.log("Waiting for post edit content update..."); const updatedContent = await contentPromise; const updatedContentString = JSON.stringify(updatedContent, null, 2); expect(updatedContentString, `${updatedContentString}`).to.include(expectedEdit); @@ -99,7 +98,6 @@ suite("Documentation Preview", function () { await expect(waitForRender(workspaceContext)).to.eventually.be.true; // Wait for the test promise to complete - console.log("Waiting for initial content update..."); const updatedContent = await contentPromise; const updatedContentString = JSON.stringify(updatedContent, null, 2); @@ -135,7 +133,6 @@ suite("Documentation Preview", function () { // FIXME: We are off by 1 right now... so need to do 1 more action // FIXME: Also the off by 1 behaviour is consistent only if on cached-run (second run and onwards) await expect(waitForRender(workspaceContext)).to.eventually.be.true; - console.log("Waiting for post edit content update..."); let updatedContent = await contentPromise; let updatedContentString = JSON.stringify(updatedContent, null, 2); expect(updatedContentString, `${updatedContentString}`).to.not.include(expectedEdit); @@ -147,7 +144,6 @@ suite("Documentation Preview", function () { // Wait for render and test promise to complete await expect(waitForRender(workspaceContext)).to.eventually.be.true; - console.log("Waiting for post edit content update, FIXME: 1+ action..."); updatedContent = await contentPromise; updatedContentString = JSON.stringify(updatedContent, null, 2); expect(updatedContentString, `${updatedContentString}`).to.include(expectedEdit); @@ -169,7 +165,6 @@ suite("Documentation Preview", function () { // Wait for render and test promise to complete await expect(waitForRender(workspaceContext)).to.eventually.be.true; - console.log("Waiting for post cursor change content update..."); const updatedContent = await contentPromise; const updatedContentString = JSON.stringify(updatedContent, null, 2); expect(updatedContentString, `${updatedContentString}`).to.include(expectedSymbol); @@ -277,7 +272,6 @@ suite("Documentation Preview", function () { // Wait for render and assert webview panel retains render of last focused editor when the panel is visible await expect(waitForRender(workspaceContext)).to.eventually.be.true; - console.log("Waiting for post visible tab change content update..."); const updatedContent = await contentPromise; const updatedContentString = JSON.stringify(updatedContent, null, 2); // FIXME: This feature is not implemented yet @@ -307,7 +301,6 @@ suite("Documentation Preview", function () { // Wait for render and assert webview panel to displayed that no documentation is available await expect(waitForRender(workspaceContext)).to.eventually.be.true; - console.log("Waiting for post extension editor change content update..."); const updatedContent = await contentPromise; const updatedContentString = JSON.stringify(updatedContent, null, 2); expect(updatedContentString, `${updatedContentString}`).to.include(