Skip to content

Commit

Permalink
R: Add snapshot tests for indentation (#2577)
Browse files Browse the repository at this point in the history
  • Loading branch information
lionel- authored Apr 2, 2024
1 parent aa2727f commit 7a3d14b
Show file tree
Hide file tree
Showing 7 changed files with 415 additions and 0 deletions.
5 changes: 5 additions & 0 deletions extensions/positron-r/src/test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Launch tests by running this from the repository root:

```sh
yarn test-extension -l positron-r
```
96 changes: 96 additions & 0 deletions extensions/positron-r/src/test/editor-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// From testUtils.ts in the typescript-language-feature extension
// https://github.com/posit-dev/positron/blob/main/extensions/typescript-language-features/src/test/testUtils.ts

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as fs from 'fs';
import * as os from 'os';
import { join } from 'path';
import * as vscode from 'vscode';

export function rndName() {
let name = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < 10; i++) {
name += possible.charAt(Math.floor(Math.random() * possible.length));
}
return name;
}

export function createRandomFile(contents = '', fileExtension = 'txt'): Thenable<vscode.Uri> {
return new Promise((resolve, reject) => {
const tmpFile = join(os.tmpdir(), rndName() + '.' + fileExtension);
fs.writeFile(tmpFile, contents, (error) => {
if (error) {
return reject(error);
}

resolve(vscode.Uri.file(tmpFile));
});
});
}

export function deleteFile(file: vscode.Uri): Thenable<boolean> {
return new Promise((resolve, reject) => {
fs.unlink(file.fsPath, (err) => {
if (err) {
reject(err);
} else {
resolve(true);
}
});
});
}

export const CURSOR = '"<>"';

export async function withFileEditor(
contents: string,
fileExtension: string,
run: (editor: vscode.TextEditor, doc: vscode.TextDocument) => Promise<void>
): Promise<void> {
const cursorIndex = contents.indexOf(CURSOR);
const rawContents = contents.replace(CURSOR, '');

const file = await createRandomFile(rawContents, fileExtension);

try {
const doc = await vscode.workspace.openTextDocument(file);
const editor = await vscode.window.showTextDocument(doc);

if (cursorIndex >= 0) {
const pos = doc.positionAt(cursorIndex);
editor.selection = new vscode.Selection(pos, pos);
}

await run(editor, doc);

if (doc.isDirty) {
await doc.save();
}
} finally {
deleteFile(file);
}
}

export const onDocumentChange = (doc: vscode.TextDocument): Promise<vscode.TextDocument> => {
return new Promise<vscode.TextDocument>(resolve => {
const sub = vscode.workspace.onDidChangeTextDocument(e => {
if (e.document !== doc) {
return;
}
sub.dispose();
resolve(e.document);
});
});
};

export const type = async (document: vscode.TextDocument, text: string): Promise<vscode.TextDocument> => {
const onChange = onDocumentChange(document);
await vscode.commands.executeCommand('type', { text });
await onChange;
return document;
};
66 changes: 66 additions & 0 deletions extensions/positron-r/src/test/indentation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import * as vscode from 'vscode';
import * as assert from 'assert';
import * as fs from 'fs';
import { CURSOR, type, withFileEditor } from './editor-utils';
import { EXTENSION_ROOT_DIR } from '../constants';
import { removeLeadingLines } from '../util';

const snapshotsFolder = `${EXTENSION_ROOT_DIR}/src/test/snapshots`;
const snippetsPath = `${snapshotsFolder}/indentation-cases.R`;
const snapshotsPath = `${snapshotsFolder}/indentation-snapshots.R`;

// FIXME: This should normally be run as part of tests setup in `before()` but
// it's somehow not defined
async function init() {
// Open workspace with custom configuration for snapshots. If you need
// custom settings set them there via `config.update()`.
const uri = vscode.Uri.file(snapshotsFolder);
await vscode.commands.executeCommand('vscode.openFolder', uri, false);
const config = vscode.workspace.getConfiguration();

// Prevents `ENOENT: no such file or directory` errors caused by us
// deleting temporary editor files befor Code had the opportunity to
// save the user history of these files.
config.update('workbench.localHistory.enabled', false, vscode.ConfigurationTarget.Workspace);
}

suite('Indentation', () => {
// This regenerates snapshots in place. If the snapshots differ from last
// run, a failure is emitted. You can either commit the new output or discard
// it if that's a bug to fix.
test('Regenerate and check', async () => {
await init();
const expected = fs.readFileSync(snapshotsPath, 'utf8');
const current = await regenerateIndentSnapshots();

// Update snapshot file
fs.writeFileSync(snapshotsPath, current, 'utf8');

// Notify if snapshots were outdated
assert.strictEqual(expected, current);
});
});

async function regenerateIndentSnapshots() {
const snippets = fs.
readFileSync(snippetsPath, 'utf8').
split('# ---\n');

// Remove documentation snippet
snippets.splice(0, 1);

const snapshots: string[] = ['# File generated from `indentation-cases.R`.\n\n'];

for (const snippet of snippets) {
const bareSnippet = snippet.split('\n').slice(0, -1).join('\n');

await withFileEditor(snippet, 'R', async (_editor, doc) => {
// Type one newline character to trigger indentation
await type(doc, `\n${CURSOR}`);
const snapshot = removeLeadingLines(doc.getText(), /^$|^#/);
snapshots.push(bareSnippet + '\n# ->\n' + snapshot);
});
}

return snapshots.join('# ---\n');
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"workbench.localHistory.enabled": false
}
82 changes: 82 additions & 0 deletions extensions/positron-r/src/test/snapshots/indentation-cases.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Indentation snapshots. Edit cases in `indentation-cases.R` and observe results
# in `indentation-snapshots.R`.
#
# The cursor position is represented by a string containing angular brackets. A
# newline is typed at that position, triggering indentation rules. The result is
# saved in the snapshot file.
#
# Snippets are separated by `# ---`. This makes it possible to extract them and
# process them separately to prevent interferences between test cases.

# ---
1 +"<>"

# ---
1 +
2 +"<>"

# ---
data |>"<>"

# ---
data |>
fn()"<>"

# ---
# https://github.com/posit-dev/positron/issues/1727
# FIXME
data |>
fn()
"<>"

# ---
# https://github.com/posit-dev/positron/issues/1316
data |>
fn() |>"<>"

# ---
# With trailing whitespace
# https://github.com/posit-dev/positron/pull/1655#issuecomment-1780093395
data |>
fn() |> "<>"

# ---
data |>
fn1() |>
fn2() |>"<>"

# ---
# FIXME
data |>
fn1() |>
fn2(
"arg"
)"<>"

# ---
# https://github.com/posit-dev/positron-beta/discussions/46
# FIXME
data |>
fn("<>")

# ---
# FIXME
{
fn(function() {}"<>")
}

# ---
# FIXME
{
fn(function() {
#
}"<>")
}

# ---
for (i in NA) NULL"<>"

# ---
# https://github.com/posit-dev/positron/issues/1880
# FIXME
for (i in 1) fn()"<>"
Loading

0 comments on commit 7a3d14b

Please sign in to comment.