diff --git a/package-lock.json b/package-lock.json index fe6bfc84..ca5bbd3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-phpunit", - "version": "3.4.30", + "version": "3.5.24", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vscode-phpunit", - "version": "3.4.30", + "version": "3.5.24", "hasInstallScript": true, "license": "MIT", "devDependencies": { diff --git a/package.json b/package.json index 09425218..7dadd46a 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "displayName": "PHPUnit Test Explorer", "icon": "img/icon.png", "publisher": "recca0120", - "version": "3.5.24", + "version": "3.5.25", "private": true, "license": "MIT", "repository": { diff --git a/src/PHPUnit/TestParser/PHPDefinition.ts b/src/PHPUnit/TestParser/PHPDefinition.ts index c934e0b0..9ab4fa92 100644 --- a/src/PHPUnit/TestParser/PHPDefinition.ts +++ b/src/PHPUnit/TestParser/PHPDefinition.ts @@ -21,6 +21,154 @@ type AST = Node & { export const annotationParser = new AnnotationParser(); export const attributeParser = new AttributeParser(); +abstract class TestDefinitionBuilder { + constructor(protected definition: PHPDefinition) { + } + + abstract build(): TestDefinition; + + protected generate(testDefinition: Partial) { + testDefinition = { + type: this.definition.type, + classFQN: this.definition.classFQN, + children: [], + annotations: this.definition.annotations, + file: this.definition.file, + ...this.definition.position, + ...testDefinition, + }; + const transformer = this.getTransformer(testDefinition); + testDefinition.id = transformer.uniqueId(testDefinition as TestDefinition); + testDefinition.label = transformer.generateLabel(testDefinition as TestDefinition); + + return testDefinition as TestDefinition; + } + + private getTransformer(testDefinition: Pick): Transformer { + return TransformerFactory.factory(testDefinition.classFQN!); + } +} + +class NamespaceDefinitionBuilder extends TestDefinitionBuilder { + build() { + const type = TestType.namespace; + const depth = 0; + + const classFQN = this.definition.classFQN; + if (this.definition.kind === 'program') { + const partsFQN = classFQN!.split('\\'); + const namespace = partsFQN.slice(0, -1).join('\\'); + + return this.generate({ type, depth, namespace, classFQN: namespace }); + } + + if (this.definition.kind === 'class') { + const partsFQN = classFQN!.split('\\'); + const className = partsFQN.pop()!; + const namespace = partsFQN.join('\\'); + + return this.generate({ type, depth, namespace, classFQN: namespace, className }); + } + + return this.generate({ type, depth, namespace: classFQN, classFQN }); + } +} + +class TestSuiteDefinitionBuilder extends TestDefinitionBuilder { + build() { + return this.generate({ + namespace: this.definition.parent?.name, + className: this.definition.name, + depth: 1, + }); + } +} + +class TestCaseDefinitionBuilder extends TestDefinitionBuilder { + build() { + return this.generate({ + namespace: this.definition.parent!.parent?.name, + className: this.definition.parent!.name, + methodName: this.definition.name, + depth: 2, + }); + } +} + +class PestTestDefinitionBuilder extends TestDefinitionBuilder { + build() { + if (this.definition.kind === 'program') { + const classFQN = this.definition.classFQN!; + const partsFQN = classFQN.split('\\'); + const className = partsFQN.pop()!; + + return this.generate({ namespace: partsFQN.join('\\'), className, depth: 1 }); + } + + let depth = 2; + + let { methodName, label } = this.parseMethodNameAndLabel(); + + if (this.definition.type === TestType.describe) { + methodName = '`' + methodName + '`'; + } + + let parent = this.definition.parent; + while (parent && parent.kind === 'call' && parent.type !== TestType.describe) { + parent = parent.parent; + } + + if (parent?.type === TestType.describe) { + const describeNames: string[] = []; + while (parent && parent.type === TestType.describe) { + describeNames.push('`' + parent.arguments[0].name + '`'); + parent = parent.parent; + depth++; + } + methodName = describeNames.reverse().concat(methodName).join(' → '); + } + + const { classFQN, namespace, className } = parent!.toTestDefinition(); + + return this.generate({ classFQN, namespace, className, methodName, label, depth }); + } + + private parseMethodNameAndLabel() { + const args = this.definition.arguments; + + if (this.definition.name !== 'arch') { + let methodName = args[0].name; + + if (this.definition.name === 'it') { + methodName = 'it ' + methodName; + } + + return { methodName, label: methodName }; + } + + if (args.length > 0) { + const methodName = args[0].name; + + return { methodName, label: methodName }; + } + + const names = [] as string[]; + let parent = this.definition.parent; + while (parent && parent.kind === 'call') { + names.push(parent.name); + parent = parent.parent; + } + + const methodName = names + .map((name: string) => name === 'preset' ? `${name} ` : ` ${name} `) + .join('→'); + + const label = names.join(' → '); + + return { methodName, label }; + } +} + export class PHPDefinition { constructor(private readonly ast: AST, private options: { phpUnitXML: PHPUnitXML, @@ -247,140 +395,19 @@ export class PHPDefinition { } toTestDefinition(): TestDefinition { - const testDefinition: Partial = { - type: this.type, - classFQN: this.classFQN, - children: [], - annotations: this.annotations, - file: this.file, - ...this.position, - }; - if (this.kind === 'class') { - testDefinition.namespace = this.parent?.name; - testDefinition.className = this.name; - testDefinition.depth = 1; + return new TestSuiteDefinitionBuilder(this).build(); } if (this.kind === 'method') { - testDefinition.namespace = this.parent!.parent?.name; - testDefinition.className = this.parent!.name; - testDefinition.methodName = this.name; - testDefinition.depth = 2; + return new TestCaseDefinitionBuilder(this).build(); } - if (this.kind === 'program') { - const classFQN = this.classFQN!; - const partsFQN = classFQN.split('\\'); - const className = partsFQN.pop()!; - testDefinition.namespace = partsFQN.join('\\'); - testDefinition.className = className; - testDefinition.depth = 1; - } - - if (this.kind === 'call') { - let depth = 2; - const args = this.arguments; - - let methodName = ''; - let label = ''; - if (this.name === 'arch') { - if (args.length > 0) { - methodName = args[0].name; - label = methodName; - } else { - const names = []; - let parent = this.parent; - while (parent && parent.kind === 'call') { - names.push(parent.name); - parent = parent.parent; - } - methodName = names - .map((name: string) => name === 'preset' ? `${name} ` : ` ${name} `) - .join('→'); - label = names.join(' → '); - } - } else { - methodName = args[0].name; - - if (this.name === 'it') { - methodName = 'it ' + methodName; - } - - label = methodName; - } - - if (this.type === TestType.describe) { - methodName = '`' + methodName + '`'; - } - - if (this.parent?.type === TestType.describe) { - const describeNames: string[] = []; - let parent: PHPDefinition | undefined = this.parent; - while (parent && parent.type === TestType.describe) { - describeNames.push('`' + parent.arguments[0].name + '`'); - parent = parent.parent; - depth++; - } - methodName = describeNames.reverse().concat(methodName).map(name => name).join(' → '); - } - - let parent = this.parent; - while (parent && parent.kind === 'call') { - parent = parent.parent; - } - - const { classFQN, namespace, className } = parent!.toTestDefinition(); - testDefinition.classFQN = classFQN; - testDefinition.namespace = namespace; - testDefinition.className = className; - testDefinition.methodName = methodName; - testDefinition.label = label; - testDefinition.depth = depth; - } - - const transformer = this.getTransformer(testDefinition); - testDefinition.id = transformer.uniqueId(testDefinition as TestDefinition); - testDefinition.label = transformer.generateLabel(testDefinition as TestDefinition); - - return testDefinition as TestDefinition; + return new PestTestDefinitionBuilder(this).build(); } createNamespaceTestDefinition(): TestDefinition { - const testDefinition: Partial = { - type: TestType.namespace, - children: [], - file: this.file, - depth: 0, - }; - - const classFQN = this.classFQN; - if (this.kind === 'program') { - const partsFQN = classFQN!.split('\\'); - const namespace = partsFQN.slice(0, -1).join('\\'); - testDefinition.namespace = namespace; - testDefinition.classFQN = namespace; - } else if (this.kind === 'class') { - const partsFQN = classFQN!.split('\\'); - const className = partsFQN.pop()!; - const namespace = partsFQN.join('\\'); - testDefinition.namespace = namespace; - testDefinition.classFQN = namespace; - testDefinition.className = className; - } else { - testDefinition.namespace = classFQN; - testDefinition.classFQN = classFQN; - } - - const transformer = this.getTransformer(testDefinition); - testDefinition.id = transformer.uniqueId(testDefinition as TestDefinition); - testDefinition.label = transformer.generateLabel(testDefinition as TestDefinition); - - return testDefinition as TestDefinition; - } - - private getTransformer(testDefinition: Pick): Transformer { - return TransformerFactory.factory(testDefinition.classFQN!); + return new NamespaceDefinitionBuilder(this).build(); } private getMethods(): PHPDefinition[] { diff --git a/src/PHPUnit/TestParser/PestParser.test.ts b/src/PHPUnit/TestParser/PestParser.test.ts index 61f05e0e..b2c2b5eb 100644 --- a/src/PHPUnit/TestParser/PestParser.test.ts +++ b/src/PHPUnit/TestParser/PestParser.test.ts @@ -375,8 +375,44 @@ arch('Then should pass the PHP preset')->preset()->php(); `; - givenTest(file, content, 'Then should pass the PHP preset'); - // expect(givenTest(file, content, 'preset → php ')).toEqual(expect.objectContaining({})); + expect(givenTest(file, content, 'Then should pass the PHP preset')).toEqual(expect.objectContaining({ + type: TestType.method, + id: 'tests/Fixtures/ExampleTest.php::Then should pass the PHP preset', + classFQN: 'P\\Tests\\Fixtures\\ExampleTest', + namespace: 'P\\Tests\\Fixtures', + className: 'ExampleTest', + methodName: 'Then should pass the PHP preset', + label: 'Then should pass the PHP preset', + file, + start: { line: expect.any(Number), character: expect.any(Number) }, + end: { line: expect.any(Number), character: expect.any(Number) }, + depth: 2, + })); + }); + + it('parse describe arch', () => { + const content = `preset()->php(); + }); +}); + + `; + expect(givenTest(file, content, '`Given a project` → `When the architecture is tested` → Then should pass the PHP preset')).toEqual(expect.objectContaining({ + type: TestType.method, + id: 'tests/Fixtures/ExampleTest.php::`Given a project` → `When the architecture is tested` → Then should pass the PHP preset', + classFQN: 'P\\Tests\\Fixtures\\ExampleTest', + namespace: 'P\\Tests\\Fixtures', + className: 'ExampleTest', + methodName: '`Given a project` → `When the architecture is tested` → Then should pass the PHP preset', + label: 'Then should pass the PHP preset', + file, + start: { line: expect.any(Number), character: expect.any(Number) }, + end: { line: expect.any(Number), character: expect.any(Number) }, + depth: 4, + })); }); }); \ No newline at end of file diff --git a/src/PHPUnit/__tests__/fixtures/pest-stub/tests/Unit/ArchTest.php b/src/PHPUnit/__tests__/fixtures/pest-stub/tests/Unit/ArchTest.php index b1b2a6bf..f94d56f6 100644 --- a/src/PHPUnit/__tests__/fixtures/pest-stub/tests/Unit/ArchTest.php +++ b/src/PHPUnit/__tests__/fixtures/pest-stub/tests/Unit/ArchTest.php @@ -5,4 +5,9 @@ arch('preset → php ')->preset()->php(); arch('preset → strict ')->preset()->strict(); arch('preset → security ')->preset()->security(); -arch('')->preset()->php(); + +describe('Given a project', function () { + describe('When the architecture is tested', function () { + arch('Then should pass the PHP preset')->preset()->php(); + }); +}); diff --git a/src/extension.test.ts b/src/extension.test.ts index 1f221ae2..a1985c05 100644 --- a/src/extension.test.ts +++ b/src/extension.test.ts @@ -428,7 +428,7 @@ describe('Extension Test', () => { if (isPestV1) { expected = { enqueued: 68, started: 62, passed: 9, failed: 51, end: 1 }; } else if (isPestV2) { - expected = { enqueued: 68, started: 65, passed: 11, failed: 52, end: 1 }; + expected = { enqueued: 68, started: 64, passed: 11, failed: 51, end: 1 }; } else { expected = { enqueued: 68, started: 70, passed: 16, failed: 52, end: 1 }; }