Skip to content

Commit

Permalink
Add focus switch tests
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-weng committed Nov 25, 2024
1 parent 9aac919 commit cd5a89f
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 55 deletions.
9 changes: 7 additions & 2 deletions src/documentation/DocumentationPreviewEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = [];
Expand All @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/utilities/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
}
191 changes: 138 additions & 53 deletions test/integration-tests/documentation/DocumentationPreview.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);

Expand All @@ -82,21 +78,20 @@ suite("Documentation Preview", function () {
uri: string,
expectedContent: string,
editToCheck: string
): Promise<{ editor: vscode.TextEditor; document: vscode.TextDocument }> {
): Promise<vscode.TextEditor> {
// 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;
Expand All @@ -111,37 +106,34 @@ 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
);

// 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;
Expand All @@ -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."
);
});
});

Expand All @@ -251,7 +336,7 @@ function waitForRender(context: WorkspaceContext): Promise<boolean> {
});
}

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
Expand All @@ -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;
}

0 comments on commit cd5a89f

Please sign in to comment.