Skip to content

Commit

Permalink
Merge pull request #270 from recca0120/feat/describe-as-suite
Browse files Browse the repository at this point in the history
Feat/describe as suite
  • Loading branch information
recca0120 authored Jan 24, 2025
2 parents a178265 + 7f643da commit 49c0a73
Show file tree
Hide file tree
Showing 20 changed files with 273 additions and 55 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"displayName": "PHPUnit Test Explorer",
"icon": "img/icon.png",
"publisher": "recca0120",
"version": "3.5.18",
"version": "3.5.19",
"private": true,
"license": "MIT",
"repository": {
Expand Down
14 changes: 9 additions & 5 deletions src/CommandHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,22 @@ export class CommandHandler {
return;
}

const test = this.testCollection.findTestByPosition(uri, window.activeTextEditor!.selection.active!);
if (test) {
await this.run([test]);
let tests = this.testCollection.findTestsByPosition(uri, window.activeTextEditor!.selection.active!);
if (tests.length > 0) {
await this.run(tests);
}
});
}

rerun(handler: Handler) {
return commands.registerCommand('phpunit.rerun', () => {
const lastRequest = handler.getPreviousRequest();
const previousRequest = handler.getPreviousRequest();

if (!previousRequest) {
return this.run(undefined);
}

return lastRequest ? this.run(lastRequest.include) : this.run(undefined);
return this.run(this.testCollection.findTestsByRequest(previousRequest));
});
}

Expand Down
21 changes: 21 additions & 0 deletions src/PHPUnit/ProblemMatcher/PestProblemMatcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -733,4 +733,25 @@ describe('Pest ProblemMatcher Text', () => {
flowId: 72545,
});
});

it('testFailed without TestStarted', () => {
resultShouldBe(`##teamcity[testFailed name='error' message='Exception: error' details='at tests/Fixtures/CollisionTest.php:4' flowId='68573']`, {
event: TeamcityEvent.testFailed,
id: 'tests/Fixtures/CollisionTest.php::error',
name: 'error',
message: 'Exception: error',
file: 'tests/Fixtures/CollisionTest.php',
flowId: 68573,
details: [
{
file: 'tests/Fixtures/CollisionTest.php',
line: 4,
},
],
});
});

it('testFinished without TestStarted', () => {
resultShouldBe('##teamcity[testFinished name=\'`before each` → example\' duration=\'12\' flowId=\'97972\']', undefined);
});
});
21 changes: 17 additions & 4 deletions src/PHPUnit/ProblemMatcher/ProblemMatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class ProblemMatcher {
[TeamcityEvent.testSuiteFinished]: this.handleFinished,
};

constructor(private testResultParser: TestResultParser = new TestResultParser()) {}
constructor(private testResultParser: TestResultParser = new TestResultParser()) { }

parse(input: string | Buffer): TestResult | undefined {
const result = this.testResultParser.parse(input.toString());
Expand All @@ -31,17 +31,24 @@ export class ProblemMatcher {

private handleStarted(testResult: TestSuiteStarted | TestStarted) {
const id = this.generateId(testResult);
this.results.set(id, { ...testResult });
this.results.set(id, testResult);

return this.results.get(id);
}

private handleFault(testResult: TestFailed | TestIgnored): undefined {
private handleFault(testResult: TestFailed | TestIgnored): TestResult | undefined {
const id = this.generateId(testResult);
const prevData = this.results.get(id) as (TestFailed | TestIgnored);
let prevData = this.results.get(id) as (TestFailed | TestIgnored);

if (!prevData) {
const file = testResult.details[0].file;

return { ...testResult, id: [file, testResult.name].join('::'), file, duration: 0 };
}

if (!prevData || prevData.event === TeamcityEvent.testStarted) {
this.results.set(id, { ...(prevData ?? {}), ...testResult });

return;
}

Expand All @@ -51,11 +58,17 @@ export class ProblemMatcher {
prevData.details.push(...testResult.details);

this.results.set(id, prevData);

return undefined;
}

private handleFinished(testResult: TestSuiteFinished | TestFinished) {
const id = this.generateId(testResult);

if (!this.results.has(id)) {
return;
}

const prevData = this.results.get(id)!;
const event = this.isFault(prevData) ? prevData.event : testResult.event;
const result = { ...prevData, ...testResult, event };
Expand Down
1 change: 1 addition & 0 deletions src/PHPUnit/TestCollection/TestDefinitionBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export class TestDefinitionBuilder {

onInit() {
this.testParser.on(TestType.method, (testDefinition) => this.testDefinitions.push(testDefinition));
this.testParser.on(TestType.describe, (testDefinition) => this.testDefinitions.push(testDefinition));
this.testParser.on(TestType.class, (testDefinition) => this.testDefinitions.push(testDefinition));
this.testParser.on(TestType.namespace, (testDefinition) => this.testDefinitions.push(testDefinition));
}
Expand Down
47 changes: 39 additions & 8 deletions src/PHPUnit/TestParser/PestParser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const parse = (buffer: Buffer | string, file: string) => {

testParser.on(TestType.namespace, (testDefinition: TestDefinition) => tests.push(testDefinition));
testParser.on(TestType.class, (testDefinition: TestDefinition) => tests.push(testDefinition));
testParser.on(TestType.describe, (testDefinition: TestDefinition) => tests.push(testDefinition));
testParser.on(TestType.method, (testDefinition: TestDefinition) => tests.push(testDefinition));
testParser.parse(buffer, file);

Expand All @@ -21,6 +22,7 @@ describe('PestParser', () => {
const findTest = (tests: TestDefinition[], id: string) => {
const lookup = {
[TestType.method]: (test: TestDefinition) => test.methodName === id,
[TestType.describe]: (test: TestDefinition) => test.methodName === id,
[TestType.class]: (test: TestDefinition) => test.className === id && !test.methodName,
[TestType.namespace]: (test: TestDefinition) => test.classFQN === id && !test.className && !test.methodName,
} as { [key: string]: Function };
Expand Down Expand Up @@ -136,25 +138,40 @@ describe('something', function () {
});
});
`;

expect(givenTest(file, content, '`something`')).toEqual(expect.objectContaining({
type: TestType.describe,
id: 'tests/Fixtures/ExampleTest.php::`something`',
classFQN: 'P\\Tests\\Fixtures\\ExampleTest',
namespace: 'P\\Tests\\Fixtures',
className: 'ExampleTest',
methodName: '`something`',
label: 'something',
file,
start: { line: expect.any(Number), character: expect.any(Number) },
end: { line: expect.any(Number), character: expect.any(Number) },
depth: 3,
}));

expect(givenTest(file, content, '`something` → example')).toEqual({
type: TestType.method,
id: 'tests/Fixtures/ExampleTest.php::`something` → example',
classFQN: 'P\\Tests\\Fixtures\\ExampleTest',
namespace: 'P\\Tests\\Fixtures',
className: 'ExampleTest',
methodName: '`something` → example',
label: 'something → example',
label: 'example',
file,
start: { line: expect.any(Number), character: expect.any(Number) },
end: { line: expect.any(Number), character: expect.any(Number) },
depth: 3,
depth: 4,
});
});

it('arrow function `something` → it example', async () => {
const content = `<?php
describe('something', fn () => it('example', expect(true)->toBeTrue()));
describe('something', fn () => it('example', fn() => expect(true)->toBeTrue()));
`;

Expand All @@ -165,11 +182,11 @@ describe('something', fn () => it('example', expect(true)->toBeTrue()));
namespace: 'P\\Tests\\Fixtures',
className: 'ExampleTest',
methodName: '`something` → it example',
label: 'something → it example',
label: 'it example',
file,
start: { line: expect.any(Number), character: expect.any(Number) },
end: { line: expect.any(Number), character: expect.any(Number) },
depth: 3,
depth: 4,
});
});

Expand All @@ -185,18 +202,32 @@ describe('something', function () {
});
`;

expect(givenTest(file, content, '`something` → `something else`')).toEqual(expect.objectContaining({
type: TestType.describe,
id: 'tests/Fixtures/ExampleTest.php::`something` → `something else`',
classFQN: 'P\\Tests\\Fixtures\\ExampleTest',
namespace: 'P\\Tests\\Fixtures',
className: 'ExampleTest',
methodName: '`something` → `something else`',
label: 'something else',
file,
start: { line: expect.any(Number), character: expect.any(Number) },
end: { line: expect.any(Number), character: expect.any(Number) },
depth: 4,
}));

expect(givenTest(file, content, '`something` → `something else` → it test example')).toEqual({
type: TestType.method,
id: 'tests/Fixtures/ExampleTest.php::`something` → `something else` → it test example',
classFQN: 'P\\Tests\\Fixtures\\ExampleTest',
namespace: 'P\\Tests\\Fixtures',
className: 'ExampleTest',
methodName: '`something` → `something else` → it test example',
label: 'something → something else → it test example',
label: 'it test example',
file,
start: { line: expect.any(Number), character: expect.any(Number) },
end: { line: expect.any(Number), character: expect.any(Number) },
depth: 3,
depth: 5,
});
});

Expand All @@ -221,7 +252,7 @@ it('example 2')->assertTrue(true);
});
});

it('not test or test', async () => {
it('not it or test', async () => {
const content = `<?php
function hello(string $description, callable $closure) {}
Expand Down
25 changes: 18 additions & 7 deletions src/PHPUnit/TestParser/PestParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,32 +60,43 @@ export class PestParser extends Parser {
.map((expressionStatement: any) => expressionStatement.expression)
.filter((call: Call) => ['describe', 'test', 'it'].includes(this.parseName(call) ?? ''))
.reduce((tests: TestDefinition[], call: Call) => {
return this.parseName(call) === 'describe'
? [...tests, ...this.parseDescribe(call, clazz, prefixes)]
: [...tests, this.parseTestOrIt(call, clazz, prefixes)];
if (this.parseName(call) !== 'describe') {
return [...tests, this.parseTestOrIt(call, clazz, prefixes)];
}

return [...tests, {
...this.parseTest(TestType.describe, call, clazz, prefixes),
children: this.parseDescribe(call, clazz, prefixes),
}];
}, []);
}

private parseTestOrIt(call: Call, clazz: TestDefinition, prefixes: string[] = []): TestDefinition {
return this.parseTest(TestType.method, call, clazz, prefixes);
}

private parseTest(type: TestType, call: Call, clazz: TestDefinition, prefixes: string[]) {
if (call.what.kind === 'propertylookup') {
return this.parseTestOrIt(((call.what as any).what) as Call, clazz, prefixes);
}

let methodName = (call.arguments[0] as String).value;

if (this.parseName(call) === 'it') {
methodName = 'it ' + methodName;
}
const labelName = methodName;

if (type === TestType.describe) {
methodName = '`' + methodName + '`';
}
if (prefixes.length > 0) {
methodName = [...prefixes.map((value) => '`' + value + '`'), methodName].join(' → ');
}

const type = TestType.method;
const id = this.transformer.uniqueId({ ...clazz, type, methodName });
const label = this.transformer.generateLabel({ ...clazz, type, methodName });
const label = this.transformer.generateLabel({ ...clazz, type, methodName: labelName });
const { start, end } = this.parsePosition(call);

return { ...clazz, type, id, label, methodName, start, end, depth: 3 };
return { ...clazz, type, id, label, methodName, start, end, depth: prefixes.length + 3 };
}
}
24 changes: 14 additions & 10 deletions src/PHPUnit/TestRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { spawn } from 'child_process';
import { ChildProcess } from 'node:child_process';
import { EventEmitter } from 'node:events';
import { CommandBuilder } from './CommandBuilder';
import { ProblemMatcher, TestResult, TeamcityEvent } from './ProblemMatcher';
import { ProblemMatcher, TeamcityEvent, TestResult } from './ProblemMatcher';
import { EventResultMap, TestRunnerEvent, TestRunnerEventProxy, TestRunnerObserver } from './TestRunnerObserver';

export class TestRunnerProcess {
Expand Down Expand Up @@ -127,15 +127,19 @@ export class TestRunner {
}

private processLine(line: string, command: CommandBuilder) {
let result = this.problemMatcher.parse(line);
if (result) {
result = command.replacePath(result);
if ('event' in result!) {
this.emit(result.event, result);
}

this.emit(TestRunnerEvent.result, result!);
}
this.emitResult(command, this.problemMatcher.parse(line));
this.emit(TestRunnerEvent.line, line);
}

private emitResult(command: CommandBuilder, result: TestResult | undefined) {
if (!result) {
return;
}

result = command.replacePath(result);
if ('event' in result!) {
this.emit(result.event, result);
}
this.emit(TestRunnerEvent.result, result!);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Tests\Feature;

use PHPUnit\Framework\TestCase;

class AuthenticationPageTest extends TestCase
{
public function test_login_screen_can_be_rendered(): void
{
self::assertTrue(true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Tests\Feature;

use PHPUnit\Framework\TestCase;

class AuthenticationTest extends TestCase
{
public function test_login_screen_can_be_rendered(): void
{
self::assertTrue(true);
}
}
5 changes: 5 additions & 0 deletions src/PHPUnit/__tests__/fixtures/pest-stub/tests/Pest.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,8 @@ function something()
{
// ..
}


if (! function_exists('describe')) {
function describe() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

describe('something', function () {
describe('something else', function () {
it('test example', function () {
expect(true)->toBeTrue();
});

it('test example2', function () {
expect(true)->toBeTrue();
});
});
});

describe('before each', function () {
test('example', function () {
expect(true)->toBeTrue();
});

it('test example', function () {
expect(true)->toBeTrue();
});
});
Loading

0 comments on commit 49c0a73

Please sign in to comment.