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; }