Skip to content

Add some roundtripping vscode tests #773

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/vscode/src/test/examples/hello.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ format: html
```

*YO!*

note the lack of newline at the end of this file.
22 changes: 22 additions & 0 deletions apps/vscode/src/test/examples/roundtrip-failures.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
``````{=markdown}
```{=markdown}
hi
```
``````

kBlockCapsuleSentinel uuid sentinel leak during SE→VE
``````{{python}}
```
dog
```
``````

`````
```{python}
a = 3
````
`````

_YO!_

be careful of newline at end of file, roundtripping removes it:
71 changes: 37 additions & 34 deletions apps/vscode/src/test/quartoDoc.test.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,65 @@
import * as vscode from "vscode";
import * as assert from "assert";
import { exampleWorkspacePath, exampleWorkspaceOutPath, copyFile, wait } from "./test-utils";
import { WORKSPACE_PATH, examplesOutUri, wait } from "./test-utils";
import { isQuartoDoc } from "../core/doc";
import { extension } from "./extension";

const APPROX_TIME_TO_OPEN_VISUAL_EDITOR = 1700;

suite("Quarto basics", function () {
// Before we run any tests, we should copy any files that get edited in the tests to file under `exampleWorkspaceOutPath`
// Before running tests, copy `./examples` to a new folder `./examples-out`.
// We copy to examples-out because the tests modify the files.
suiteSetup(async function () {
const didCopyFile = await copyFile(exampleWorkspacePath('hello.qmd'), exampleWorkspaceOutPath('hello.qmd'));
assert.ok(didCopyFile);
await vscode.workspace.fs.delete(examplesOutUri(), { recursive: true });
await vscode.workspace.fs.copy(vscode.Uri.file(WORKSPACE_PATH), examplesOutUri());
});

test("Can open a Quarto document", async function () {
const doc = await vscode.workspace.openTextDocument(exampleWorkspaceOutPath("hello.qmd"));
const doc = await vscode.workspace.openTextDocument(examplesOutUri("hello.qmd"));
const editor = await vscode.window.showTextDocument(doc);

assert.strictEqual(editor?.document.languageId, "quarto");
assert.strictEqual(isQuartoDoc(editor?.document), true);
});

// Note: the following tests may be flaky. They rely on waiting estimated amounts of time for commands to complete.
test("Can edit in visual mode", async function () {
const doc = await vscode.workspace.openTextDocument(exampleWorkspaceOutPath("hello.qmd"));
const editor = await vscode.window.showTextDocument(doc);

await extension().activate();

// manually confirm visual mode so dialogue pop-up doesn't show because dialogues cause test errors
// and switch to visual editor
await vscode.commands.executeCommand("quarto.test_setkVisualModeConfirmedTrue");
await wait(300); // It seems necessary to wait around 300ms for this command to be done.
await vscode.commands.executeCommand("quarto.editInVisualMode");
await wait(APPROX_TIME_TO_OPEN_VISUAL_EDITOR);

assert.ok(await vscode.commands.executeCommand("quarto.test_isInVisualEditor"));
});
// Note: this test runs after the previous test, so `hello.qmd` has already been touched by the previous
// Note: this test runs after the previous test, so `hello.qmd` can already be touched by the previous
// test. That's okay for this test, but could cause issues if you expect a qmd to look how it
// does in `/examples`.
test("Roundtrip doesn't change hello.qmd", async function () {
const doc = await vscode.workspace.openTextDocument(exampleWorkspaceOutPath("hello.qmd"));
const doc = await vscode.workspace.openTextDocument(examplesOutUri("hello.qmd"));
const editor = await vscode.window.showTextDocument(doc);

await extension().activate();
const { before, after } = await roundtrip(doc);

const docTextBefore = doc.getText();
assert.equal(before, after);
});

// switch to visual editor and back
await vscode.commands.executeCommand("quarto.test_setkVisualModeConfirmedTrue");
await wait(300);
await vscode.commands.executeCommand("quarto.editInVisualMode");
await wait(APPROX_TIME_TO_OPEN_VISUAL_EDITOR);
await vscode.commands.executeCommand("quarto.editInSourceMode");
await wait(300);
test("Roundtrip does change roundtrip-failures.qmd", async function () {
// We want this test to fail locally so that we can reference the
// before/affter diff that Mocha logs, but we dont wan't CI to fail.
if (process.env['CI']) this.skip();

const docTextAfter = doc.getText();
assert.ok(docTextBefore === docTextAfter, docTextAfter);
const doc = await vscode.workspace.openTextDocument(examplesOutUri("roundtrip-failures.qmd"));
const editor = await vscode.window.showTextDocument(doc);

const { before, after } = await roundtrip(doc);

assert.equal(before, after);
});
});

async function roundtrip(doc: vscode.TextDocument) {
const before = doc.getText();

// switch to visual editor and back
await vscode.commands.executeCommand("quarto.test_setkVisualModeConfirmedTrue");
await wait(300);
await vscode.commands.executeCommand("quarto.editInVisualMode");
await wait(APPROX_TIME_TO_OPEN_VISUAL_EDITOR);
await vscode.commands.executeCommand("quarto.editInSourceMode");
await wait(300);

const after = doc.getText();

return { before, after };
}
30 changes: 3 additions & 27 deletions apps/vscode/src/test/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,36 +13,12 @@ export const EXTENSION_ROOT_DIR =

export const TEST_PATH = path.join(EXTENSION_ROOT_DIR, "src", "test");
export const WORKSPACE_PATH = path.join(TEST_PATH, "examples");
export const WORKSPACE_OUT_PATH = path.join(TEST_PATH, "examples-out");

export function exampleWorkspacePath(file: string): string {
return path.join(WORKSPACE_PATH, file);
}
export function exampleWorkspaceOutPath(file: string): string {
return path.join(WORKSPACE_PATH, 'examples-out', file);
export function examplesOutUri(fileName: string = ''): vscode.Uri {
return vscode.Uri.file(path.join(WORKSPACE_OUT_PATH, fileName));
}

export function wait(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}

export async function copyFile(
sourcePath: string,
destPath: string,
): Promise<boolean> {
try {
const wsedit = new vscode.WorkspaceEdit();
const data = await vscode.workspace.fs.readFile(
vscode.Uri.file(sourcePath)
);
const destFileUri = vscode.Uri.file(destPath);
wsedit.createFile(destFileUri, { ignoreIfExists: true });

await vscode.workspace.fs.writeFile(destFileUri, data);

let isDone = await vscode.workspace.applyEdit(wsedit);
if (isDone) return true;
else return false;
} catch (err) {
return false;
}
}