Skip to content

Commit

Permalink
Add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
domsleee committed Oct 15, 2023
1 parent 09fed25 commit 0bb7c08
Show file tree
Hide file tree
Showing 14 changed files with 1,599 additions and 1,382 deletions.
2,472 changes: 1,198 additions & 1,274 deletions package-lock.json

Large diffs are not rendered by default.

36 changes: 19 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -206,23 +206,25 @@
"prettier": "prettier --write"
},
"devDependencies": {
"@types/jest": "^29.5.5",
"@types/node": "^20.8.6",
"@types/vscode": "^1.83.0",
"@typescript-eslint/eslint-plugin": "^6.7.5",
"@typescript-eslint/parser": "^6.7.5",
"eslint": "^8.51.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.1",
"jest": "^29.7.0",
"ovsx": "^0.8.3",
"prettier": "^3.0.3",
"ts-jest": "^29.1.1",
"ts-loader": "^9.5.0",
"typescript": "^5.2.2",
"@vscode/vsce": "^2.21.1",
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4"
"@jest/types": "^29.1.2",
"@types/jest": "^29.1.2",
"@types/node": "^18.8.5",
"@types/vscode": "^1.72.0",
"@typescript-eslint/eslint-plugin": "^5.40.0",
"@typescript-eslint/parser": "^5.40.0",
"eslint": "^8.25.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.2.1",
"jest": "^29.1.2",
"ovsx": "^0.5.1",
"prettier": "^2.7.1",
"rimraf": "^5.0.5",
"ts-jest": "^29.0.3",
"ts-loader": "^9.4.1",
"typescript": "^4.8.4",
"vsce": "^2.11.0",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0"
},
"dependencies": {
"jest-editor-support": "^31.1.1"
Expand Down
11 changes: 7 additions & 4 deletions src/JestRunnerCodeLensProvider.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { parse, ParsedNode } from './parser';
import { CodeLens, CodeLensProvider, Range, TextDocument } from 'vscode';
import { findFullTestName, escapeRegExp, CodeLensOption } from './util';
import { findFullTestName, CodeLensOption } from './codeLensUtil';

function getCodeLensForOption(range: Range, codeLensOption: CodeLensOption, fullTestName: string): CodeLens {
const titleMap: Record<CodeLensOption, string> = {
Expand Down Expand Up @@ -44,7 +44,7 @@ function getTestsBlocks(
return [];
}

const fullTestName = escapeRegExp(findFullTestName(parsedNode.start.line, parseResults));
const fullTestName = findFullTestName(parsedNode.start.line, parseResults);

codeLens.push(...codeLensOptions.map((option) => getCodeLensForOption(range, option, fullTestName)));

Expand All @@ -55,9 +55,12 @@ export class JestRunnerCodeLensProvider implements CodeLensProvider {
constructor(private readonly codeLensOptions: CodeLensOption[]) {}

public async provideCodeLenses(document: TextDocument): Promise<CodeLens[]> {
return this.getCodeLenses(document.fileName, document.getText());
}

public async getCodeLenses(documentFileName: string, documentText: string): Promise<CodeLens[]> {
try {
const text = document.getText();
const parseResults = parse(document.fileName, text, { plugins: { decorators: 'legacy' } }).root.children;
const parseResults = parse(documentFileName, documentText, { plugins: { decorators: 'legacy' } }).root.children;
const codeLens: CodeLens[] = [];
parseResults.forEach((parseResult) =>
codeLens.push(...getTestsBlocks(parseResult, parseResults, this.codeLensOptions))
Expand Down
29 changes: 29 additions & 0 deletions src/codeLensUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export function findFullTestName(selectedLine: number, children: any[]): string | undefined {
if (!children) {
return;
}
for (const element of children) {
if (element.type === 'describe' && selectedLine === element.start.line) {
return element.name;
}
if (element.type !== 'describe' && selectedLine >= element.start.line && selectedLine <= element.end.line) {
return element.name;
}
}
for (const element of children) {
const result = findFullTestName(selectedLine, element.children);
if (result) {
return element.name + ' ' + result;
}
}
}

export type CodeLensOption = 'run' | 'debug' | 'watch' | 'coverage';

function isCodeLensOption(option: string): option is CodeLensOption {
return ['run', 'debug', 'watch', 'coverage'].includes(option);
}

export function validateCodeLensOptions(maybeCodeLensOptions: string[]): CodeLensOption[] {
return [...new Set(maybeCodeLensOptions)].filter((value) => isCodeLensOption(value)) as CodeLensOption[];
}
57 changes: 57 additions & 0 deletions src/commandBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { IJestRunnerCommandBuilderConfig } from './jestRunnerConfig';
import {
escapeRegExp,
escapeRegExpForPath,
escapeQuotesInTestName,
normalizePath,
quote,
resolveTestNameStringInterpolation,
updateTestNameIfUsingProperties,
} from './util';

export class CommandBuilder {
constructor(private readonly config: IJestRunnerCommandBuilderConfig) {}

buildJestCommand(filePath: string, testName?: string, options?: string[]): string {
const args = this.buildJestArgs(filePath, testName, options);
return `${this.config.jestCommand} ${args.join(' ')}`;
}

buildJestArgs(filePath: string, testName: string | undefined, options: string[] = []): string[] {
const args: string[] = [];

args.push(quote(escapeRegExpForPath(normalizePath(filePath))));

const jestConfigPath = this.config.getJestConfigPath(filePath);
if (jestConfigPath) {
args.push('-c');
args.push(quote(normalizePath(jestConfigPath)));
}

if (testName) {
args.push('-t');
testName = resolveTestName(testName);
args.push(quote(testName));
}

const setOptions = new Set(options);

if (this.config.runOptions) {
this.config.runOptions.forEach((option) => setOptions.add(option));
}

args.push(...setOptions);

return args;
}
}

function resolveTestName(testName: string): string {
testName = updateTestNameIfUsingProperties(testName);
testName = resolveTestNameStringInterpolation(testName);
testName = escapeRegExp(testName);
testName = escapeQuotesInTestName(testName);
testName = testName.replace(/\n/g, '\\n');
testName = testName.replace(/\r/g, '\\r');
return testName;
}
64 changes: 12 additions & 52 deletions src/jestRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,9 @@ import * as vscode from 'vscode';

import { JestRunnerConfig } from './jestRunnerConfig';
import { parse } from './parser';
import {
escapeRegExp,
escapeRegExpForPath,
escapeSingleQuotes,
findFullTestName,
normalizePath,
pushMany,
quote,
unquote,
updateTestNameIfUsingProperties,
} from './util';
import { pushMany, quote, unquote } from './util';
import { CommandBuilder } from './commandBuilder';
import { findFullTestName } from './codeLensUtil';

interface DebugCommand {
documentUri: vscode.Uri;
Expand All @@ -29,9 +21,11 @@ export class JestRunner {
// terminal after all commands been pushed
private openNativeTerminal: boolean;
private commands: string[] = [];
private readonly commandBuilder: CommandBuilder;

constructor(private readonly config: JestRunnerConfig) {
this.setup();
this.commandBuilder = new CommandBuilder(this.config);
this.openNativeTerminal = config.isRunInExternalNativeTerminal;
}

Expand All @@ -40,7 +34,7 @@ export class JestRunner {
//

public async runTestsOnPath(path: string): Promise<void> {
const command = this.buildJestCommand(path);
const command = this.commandBuilder.buildJestCommand(path);

this.previousCommand = command;

Expand All @@ -61,8 +55,7 @@ export class JestRunner {

const filePath = editor.document.fileName;
const testName = currentTestName || this.findCurrentTestName(editor);
const resolvedTestName = updateTestNameIfUsingProperties(testName);
const command = this.buildJestCommand(filePath, resolvedTestName, options);
const command = this.commandBuilder.buildJestCommand(filePath, testName, options);

this.previousCommand = command;

Expand All @@ -81,7 +74,7 @@ export class JestRunner {
await editor.document.save();

const filePath = editor.document.fileName;
const command = this.buildJestCommand(filePath, undefined, options);
const command = this.commandBuilder.buildJestCommand(filePath, undefined, options);

this.previousCommand = command;

Expand Down Expand Up @@ -131,8 +124,7 @@ export class JestRunner {

const filePath = editor.document.fileName;
const testName = currentTestName || this.findCurrentTestName(editor);
const resolvedTestName = updateTestNameIfUsingProperties(testName);
const debugConfig = this.getDebugConfig(filePath, resolvedTestName);
const debugConfig = this.getDebugConfig(filePath, testName);

await this.goToCwd();
await this.executeDebugCommand({
Expand Down Expand Up @@ -180,7 +172,7 @@ export class JestRunner {
config.program = `.yarn/releases/${this.config.getYarnPnpCommand}`;
}

const standardArgs = this.buildJestArgs(filePath, currentTestName, false);
const standardArgs = this.commandBuilder.buildJestArgs(filePath, currentTestName);
pushMany(config.args, standardArgs);
config.args.push('--runInBand');

Expand All @@ -199,40 +191,7 @@ export class JestRunner {
const testFile = parse(filePath);

const fullTestName = findFullTestName(selectedLine, testFile.root.children);
return fullTestName ? escapeRegExp(fullTestName) : undefined;
}

private buildJestCommand(filePath: string, testName?: string, options?: string[]): string {
const args = this.buildJestArgs(filePath, testName, true, options);
return `${this.config.jestCommand} ${args.join(' ')}`;
}

private buildJestArgs(filePath: string, testName: string, withQuotes: boolean, options: string[] = []): string[] {
const args: string[] = [];
const quoter = withQuotes ? quote : (str) => str;

args.push(quoter(escapeRegExpForPath(normalizePath(filePath))));

const jestConfigPath = this.config.getJestConfigPath(filePath);
if (jestConfigPath) {
args.push('-c');
args.push(quoter(normalizePath(jestConfigPath)));
}

if (testName) {
args.push('-t');
args.push(quoter(escapeSingleQuotes(testName)));
}

const setOptions = new Set(options);

if (this.config.runOptions) {
this.config.runOptions.forEach((option) => setOptions.add(option));
}

args.push(...setOptions);

return args;
return fullTestName;
}

private async goToCwd() {
Expand Down Expand Up @@ -273,6 +232,7 @@ export class JestRunner {
}

if (!this.terminal) {
console.log(vscode.workspace.getConfiguration('terminal.integrated').get('shell'));
this.terminal = vscode.window.createTerminal('jest');
}
this.terminal.show(this.config.preserveEditorFocus);
Expand Down
11 changes: 9 additions & 2 deletions src/jestRunnerConfig.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import * as path from 'path';
import * as fs from 'fs';
import * as vscode from 'vscode';
import { normalizePath, quote, validateCodeLensOptions, CodeLensOption, isNodeExecuteAbleFile } from './util';
import { normalizePath, quote, isNodeExecuteAbleFile } from './util';
import { CodeLensOption, validateCodeLensOptions } from './codeLensUtil';

export class JestRunnerConfig {
export class JestRunnerConfig implements IJestRunnerCommandBuilderConfig {
/**
* The command that runs jest.
* Defaults to: node "node_modules/.bin/jest"
Expand Down Expand Up @@ -161,3 +162,9 @@ export class JestRunnerConfig {
return yarnPnpCommand;
}
}

export interface IJestRunnerCommandBuilderConfig {
jestCommand: string;
getJestConfigPath(filePath: string): string;
runOptions?: Array<string>;
}
77 changes: 77 additions & 0 deletions src/test/commandBuilder.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { tmpdir } from 'os';
import { mkdtempSync } from 'fs';
import { rimrafSync } from 'rimraf';
import * as path from 'path';
import { runJestCommand } from './util/runJestCommand';
import { Shell } from './util/shellHandler';
import { isWindows } from '../util';

const ALL_SHELLS: Array<Shell> = isWindows() ? ['cmd', 'powershell'] : ['bash'];

describe('CommandBuilder', () => {
let tempDir: string;

beforeEach(() => {
tempDir = mkdtempSync(path.resolve(tmpdir(), 'commandBuilder'));
});

afterEach(() => {
rimrafSync(tempDir);
});

describe.each(ALL_SHELLS)('mustMatchSelf (%s)', (shell: Shell) => {
it('single quote', () => mustMatchSelf(shell, `test with ' single quote`));
it('double quote', () => mustMatchSelf(shell, 'test with " double quote'));
it('parenthesis', () => mustMatchSelf(shell, 'test with () parenthesis'));
it('lf', () => mustMatchSelf(shell, `test with \nlf`));
it('lf#2', () => mustMatchSelf(shell, 'test with \nmanual lf'));
it('crlf', () => mustMatchSelf(shell, 'test with \r\nmanual crlf'));
it('backticks', () => mustMatchSelf(shell, 'test with `backticks`'));
it('regex', () => mustMatchSelf(shell, 'test with regex .*$^|[]'));
it('prototype .name property', () => mustMatchSelf(shell, 'TestClass.prototype.myFunction.name', 'myFunction'));
});

async function mustMatchSelf(shell: Shell, testName: string, expectedTestName?: string) {
if (shouldSkipMustMatchSelf(shell, testName)) {
return;
}
const jestJson = await runJestCommand(shell, tempDir, testName);
const expectedPassedTests = [expectedTestName ?? testName];
return expect(jestJson).toEqual(
expect.objectContaining({
passedTests: expectedPassedTests,
})
);
}

// FIXME: these are broken
function shouldSkipMustMatchSelf(shell: Shell, testName: string): boolean {
if (isWindows()) {
if (shell === 'powershell' && testName === 'test with `backticks`') {
return true;
}
if (shell === 'powershell' && testName === 'test with " double quote') {
return true;
}
} else {
return shell === 'pwsh' && testName === `test with ' single quote`;
}

return false;
}

describe.each(ALL_SHELLS)('mustMatchAll (%s)', (shell: Shell) => {
it('all match', () => mustMatchAll(shell, 'test with '));
it('using %', () => mustMatchAll(shell, 'test with %p'));
it('using $', () => mustMatchAll(shell, 'test with $var'));
});

async function mustMatchAll(shell: Shell, testName: string) {
const jestJson = await runJestCommand(shell, tempDir, testName);
expect(jestJson).toEqual(
expect.objectContaining({
numPassedTests: 15,
})
);
}
});
Loading

0 comments on commit 0bb7c08

Please sign in to comment.