Skip to content

Commit

Permalink
Expose parse result (#773)
Browse files Browse the repository at this point in the history
  • Loading branch information
connectdotz committed Sep 29, 2021
1 parent 4f7359a commit cebc7e8
Show file tree
Hide file tree
Showing 12 changed files with 206 additions and 49 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Please add your own contribution below inside the Master section
## Master
* brief change description - @author
* expose test file parse result to TestExplorer so the status indicators and test item menu are accessible for all run modes. - @connectdotz
-->

### 4.1.2
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "vscode-jest",
"displayName": "Jest",
"description": "Use Facebook's Jest With Pleasure.",
"version": "4.1.2",
"version": "4.2.0-rc.1",
"publisher": "Orta",
"engines": {
"vscode": "^1.59.0"
Expand Down
1 change: 0 additions & 1 deletion src/JestExt/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,6 @@ export class JestExt {
onWillSaveTextDocument(event: vscode.TextDocumentWillSaveEvent): void {
if (event.document.isDirty) {
this.removeCachedTestResults(event.document, true);
this.refreshDocumentChange(event.document);
}
}
onDidSaveTextDocument(document: vscode.TextDocument): void {
Expand Down
6 changes: 6 additions & 0 deletions src/TestResults/TestResultProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,12 @@ export class TestResultProvider {
error = `encountered internal match error: ${e}`;
}

this.events.testSuiteChanged.fire({
type: 'test-parsed',
file: filePath,
testContainer: match.buildSourceContainer(root),
});

// no need to do groupByRange as the source block will not have blocks under the same location
return {
status: 'Unknown',
Expand Down
7 changes: 7 additions & 0 deletions src/TestResults/test-result-events.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { ItBlock } from 'jest-editor-support';
import * as vscode from 'vscode';
import { JestProcessInfo } from '../JestProcessManagement';
import { ContainerNode } from './match-node';

export type TestSuiteChangeReason = 'assertions-updated' | 'result-matched';
export type TestSuitChangeEvent =
Expand All @@ -11,6 +13,11 @@ export type TestSuitChangeEvent =
| {
type: 'result-matched';
file: string;
}
| {
type: 'test-parsed';
file: string;
testContainer: ContainerNode<ItBlock>;
};

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
Expand Down
76 changes: 48 additions & 28 deletions src/test-provider/test-item-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { JestRunEvent } from '../JestExt';
import { TestSuiteResult } from '../TestResults';
import * as path from 'path';
import { JestExtRequestType } from '../JestExt/process-session';
import { TestAssertionStatus } from 'jest-editor-support';
import { DataNode, NodeType, ROOT_NODE_NAME } from '../TestResults/match-node';
import { ItBlock, TestAssertionStatus } from 'jest-editor-support';
import { ContainerNode, DataNode, NodeType, ROOT_NODE_NAME } from '../TestResults/match-node';
import { Logging } from '../logging';
import { TestSuitChangeEvent } from '../TestResults/test-result-events';
import { Debuggable, TestItemData, TestItemRun } from './types';
Expand Down Expand Up @@ -154,7 +154,7 @@ export class WorkspaceRoot extends TestItemDataBase {
*/
private addTestFile = (
absoluteFileName: string,
onTestDocument?: (doc: TestDocumentRoot) => void
onTestDocument: (doc: TestDocumentRoot) => void
): TestDocumentRoot => {
let docRoot = this.testDocuments.get(absoluteFileName);
if (!docRoot) {
Expand All @@ -165,7 +165,7 @@ export class WorkspaceRoot extends TestItemDataBase {
this.testDocuments.set(absoluteFileName, docRoot);
}

onTestDocument?.(docRoot);
onTestDocument(docRoot);

return docRoot;
};
Expand Down Expand Up @@ -224,6 +224,11 @@ export class WorkspaceRoot extends TestItemDataBase {
this.addTestFile(event.file, (testRoot) => testRoot.onTestMatched());
break;
}
case 'test-parsed': {
this.addTestFile(event.file, (testRoot) =>
testRoot.discoverTest(undefined, event.testContainer)
);
}
}
};

Expand Down Expand Up @@ -353,11 +358,17 @@ export class FolderData extends TestItemDataBase {
}
}

const isDataNode = (arg: NodeType<TestAssertionStatus>): arg is DataNode<TestAssertionStatus> =>
type ItemNodeType = NodeType<ItBlock | TestAssertionStatus>;
type ItemDataNodeType = DataNode<ItBlock | TestAssertionStatus>;
const isDataNode = (arg: ItemNodeType): arg is ItemDataNodeType =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(arg as any).data != null;

type AssertNode = NodeType<TestAssertionStatus>;
const isAssertDataNode = (arg: ItemNodeType): arg is DataNode<TestAssertionStatus> =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
isDataNode(arg) && (arg.data as any).fullName;

// type AssertNode = NodeType<TestAssertionStatus>;
abstract class TestResultData extends TestItemDataBase {
constructor(readonly context: JestTestProviderContext, name: string) {
super(context, name);
Expand All @@ -381,16 +392,25 @@ abstract class TestResultData extends TestItemDataBase {
run.skipped(this.item);
break;
case 'KnownFail': {
const message = new vscode.TestMessage(result.message);
message.location = errorLocation;
if (
this.context.ext.settings.testExplorer.enabled &&
this.context.ext.settings.testExplorer.showInlineError
) {
const message = new vscode.TestMessage(result.message);
if (errorLocation) {
message.location = errorLocation;
}

run.failed(this.item, message);
run.failed(this.item, message);
} else {
run.failed(this.item, []);
}
break;
}
}
}

makeTestId(fileUri: vscode.Uri, target?: NodeType<TestAssertionStatus>, extra?: string): string {
makeTestId(fileUri: vscode.Uri, target?: ItemNodeType, extra?: string): string {
const parts = [fileUri.fsPath];
if (target && target.name !== ROOT_NODE_NAME) {
parts.push(target.fullName);
Expand All @@ -409,7 +429,7 @@ abstract class TestResultData extends TestItemDataBase {
const truncateExtra = (id: string): string => id.replace(/(.*)(#[0-9]+$)/, '$1');
return truncateExtra(id1) === truncateExtra(id2);
}
syncChildNodes(node: AssertNode): void {
syncChildNodes(node: ItemNodeType): void {
const testId = this.makeTestId(this.uri, node);
if (!this.isSameId(testId, this.item.id)) {
this.item.error = 'invalid node';
Expand All @@ -419,12 +439,12 @@ abstract class TestResultData extends TestItemDataBase {

if (!isDataNode(node)) {
const idMap = [...node.childContainers, ...node.childData]
.flatMap((n) => n.getAll() as AssertNode[])
.flatMap((n) => n.getAll() as ItemDataNodeType[])
.reduce((map, node) => {
const id = this.makeTestId(this.uri, node);
map.set(id, map.get(id)?.concat(node) ?? [node]);
return map;
}, new Map<string, AssertNode[]>());
}, new Map<string, ItemDataNodeType[]>());

const newItems: vscode.TestItem[] = [];
idMap.forEach((nodes, id) => {
Expand Down Expand Up @@ -478,18 +498,22 @@ export class TestDocumentRoot extends TestResultData {
return item;
}

discoverTest = (run: vscode.TestRun): void => {
this.createChildItems();
this.updateResultState(run);
discoverTest = (run?: vscode.TestRun, parsedRoot?: ContainerNode<ItBlock>): void => {
this.createChildItems(parsedRoot);
if (run) {
this.updateResultState(run);
}
};

private createChildItems = (): void => {
private createChildItems = (parsedRoot?: ContainerNode<ItBlock>): void => {
try {
const suiteResult = this.context.ext.testResolveProvider.getTestSuiteResult(this.item.id);
if (!suiteResult || !suiteResult.assertionContainer) {
const container =
parsedRoot ??
this.context.ext.testResolveProvider.getTestSuiteResult(this.item.id)?.assertionContainer;
if (!container) {
this.item.children.replace([]);
} else {
this.syncChildNodes(suiteResult.assertionContainer);
this.syncChildNodes(container);
}
} catch (e) {
this.log('error', `[TestDocumentRoot] "${this.item.id}" createChildItems failed:`, e);
Expand Down Expand Up @@ -524,7 +548,7 @@ export class TestData extends TestResultData implements Debuggable {
constructor(
readonly context: JestTestProviderContext,
fileUri: vscode.Uri,
private node: AssertNode,
private node: ItemNodeType,
parent: vscode.TestItem,
extraId?: string
) {
Expand Down Expand Up @@ -575,7 +599,7 @@ export class TestData extends TestResultData implements Debuggable {
this.item.range = undefined;
}

updateNode(node: NodeType<TestAssertionStatus>): void {
updateNode(node: ItemNodeType): void {
this.node = node;
this.updateItemRange();
this.syncChildNodes(node);
Expand All @@ -590,14 +614,10 @@ export class TestData extends TestResultData implements Debuggable {
}

public updateResultState(run: vscode.TestRun): void {
if (this.node && isDataNode(this.node)) {
if (this.node && isAssertDataNode(this.node)) {
const assertion = this.node.data;
const errorLine =
assertion.line != null &&
this.context.ext.settings.testExplorer.enabled &&
this.context.ext.settings.testExplorer.showInlineError
? this.createLocation(this.uri, assertion.line - 1)
: undefined;
assertion.line != null ? this.createLocation(this.uri, assertion.line - 1) : undefined;
this.updateItemState(run, assertion, errorLine);
}
this.item.children.forEach((childItem) =>
Expand Down
3 changes: 3 additions & 0 deletions src/test-provider/test-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ export class JestTestProvider {

private discoverTest = (item: vscode.TestItem | undefined): void => {
const theItem = item ?? this.workspaceRoot.item;
if (!theItem.canResolveChildren) {
return;
}
const run = this.context.createTestRun(
new vscode.TestRunRequest([theItem]),
`disoverTest: ${this.controller.id}`
Expand Down
13 changes: 12 additions & 1 deletion tests/TestResults/TestResultProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,14 +210,15 @@ describe('TestResultProvider', () => {
});

it('unmatched test should report the reason', () => {
const sut = newProviderWithData([makeData([testBlock], [], filePath)]);
const sut = newProviderWithData([makeData([testBlock], null, filePath)]);
const actual = sut.getResults(filePath);

expect(actual).toHaveLength(1);
expect(actual[0].status).toBe(TestReconciliationState.Unknown);
expect(actual[0].shortMessage).not.toBeUndefined();
expect(actual[0].terseMessage).toBeUndefined();
});

it('fire testSuiteChanged event for newly matched result', () => {
const sut = newProviderWithData([makeData([testBlock], [], filePath)]);
sut.getResults(filePath);
Expand All @@ -226,6 +227,16 @@ describe('TestResultProvider', () => {
file: filePath,
});
});
it('unmatched test will file test-parsed event instead', () => {
const sut = newProviderWithData([makeData([testBlock], null, filePath)]);
sut.getResults(filePath);
expect(sut.events.testSuiteChanged.fire).toBeCalledWith({
type: 'test-parsed',
file: filePath,
testContainer: expect.anything(),
});
});

describe('duplicate test names', () => {
const testBlock2 = helper.makeItBlock(testBlock.name, [5, 3, 7, 5]);
beforeEach(() => {});
Expand Down
1 change: 1 addition & 0 deletions tests/test-provider/test-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const mockExtExplorerContext = (wsName = 'ws-1', override: any = {}): any
},
debugTests: jest.fn(),
sessionEvents: mockJestExtEvents(),
settings: { testExplorer: { enabled: true } },
...override,
};
};
Expand Down
49 changes: 43 additions & 6 deletions tests/test-provider/test-item-data.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ import {
} from '../../src/test-provider/test-item-data';
import * as helper from '../test-helper';
import { JestTestProviderContext } from '../../src/test-provider/test-provider-context';
import { buildAssertionContainer } from '../../src/TestResults/match-by-context';
import {
buildAssertionContainer,
buildSourceContainer,
} from '../../src/TestResults/match-by-context';
import * as path from 'path';
import { mockController, mockExtExplorerContext } from './test-helper';

Expand Down Expand Up @@ -79,7 +82,6 @@ describe('test-item-data', () => {
.mockImplementation((uri, p) => ({ fsPath: `${uri.fsPath}/${p}` }));
vscode.Uri.file = jest.fn().mockImplementation((f) => ({ fsPath: f }));
});

describe('discover children', () => {
describe('WorkspaceRoot', () => {
it('create test document tree for testFiles list', () => {
Expand Down Expand Up @@ -225,6 +227,7 @@ describe('test-item-data', () => {
describe('when testSuiteChanged.assertions-updated event filed', () => {
it('all item data will be updated accordingly', () => {
context.ext.testResolveProvider.getTestList.mockReturnValueOnce([]);
context.ext.settings = { testExplorer: { enabled: true, showInlineError: true } };

const wsRoot = new WorkspaceRoot(context);
wsRoot.discoverTest(runMock);
Expand Down Expand Up @@ -343,6 +346,41 @@ describe('test-item-data', () => {
});
});
});
describe('when testSuiteChanged.test-parsed event filed', () => {
it('test items will be added based on parsed test files (test blocks)', () => {
// assertion should be discovered prior
context.ext.testResolveProvider.getTestList.mockReturnValueOnce(['/ws-1/a.test.ts']);

const t1 = helper.makeItBlock('test-1', [1, 1, 5, 1]);
const t2 = helper.makeItBlock('test-2', [6, 1, 7, 1]);
const sourceRoot = helper.makeRoot([t2, t1]);
const testContainer = buildSourceContainer(sourceRoot);

const wsRoot = new WorkspaceRoot(context);
wsRoot.discoverTest(runMock);
expect(context.ext.testResolveProvider.getTestSuiteResult).toHaveBeenCalledTimes(1);

expect(wsRoot.item.children.size).toBe(1);
const docItem = getChildItem(wsRoot.item, 'a.test.ts');
expect(docItem.children.size).toEqual(0);
controllerMock.createTestRun.mockClear();
context.ext.testResolveProvider.getTestSuiteResult.mockClear();

context.ext.testResolveProvider.events.testSuiteChanged.event.mock.calls[0][0]({
type: 'test-parsed',
file: '/ws-1/a.test.ts',
testContainer,
});
expect(docItem.children.size).toEqual(2);
let dItem = getChildItem(docItem, 'test-1');
expect(dItem.range).toEqual({ args: [0, 0, 4, 0] });
dItem = getChildItem(docItem, 'test-2');
expect(dItem.range).toEqual({ args: [5, 0, 6, 0] });

expect(context.ext.testResolveProvider.getTestSuiteResult).not.toHaveBeenCalled();
expect(controllerMock.createTestRun).not.toBeCalled();
});
});
});
});
describe('TestDocumentRoot', () => {
Expand Down Expand Up @@ -465,6 +503,7 @@ describe('test-item-data', () => {
const file = '/ws-1/a.test.ts';
let wsRoot;
beforeEach(() => {
jest.clearAllMocks();
context.ext.testResolveProvider.getTestList.mockReturnValueOnce([file]);
wsRoot = new WorkspaceRoot(context);

Expand Down Expand Up @@ -565,11 +604,9 @@ describe('test-item-data', () => {
const tItem = getChildItem(dItem, 'test-b');
expect(runMock.failed).toBeCalledWith(tItem, expect.anything());
if (hasLocation) {
expect(vscode.Location).toHaveBeenCalledWith(tItem.uri, expect.anything());
expect(vscode.Position).toBeCalledTimes(2);
expect(vscode.Position).toBeCalledWith(12, 0);
expect(vscode.TestMessage).toBeCalled();
} else {
expect(vscode.Location).not.toBeCalled();
expect(vscode.TestMessage).not.toBeCalled();
}
}
);
Expand Down
Loading

0 comments on commit cebc7e8

Please sign in to comment.