From 75857ecbb988d96781a309d268af9270a972e6a5 Mon Sep 17 00:00:00 2001 From: Christoffer Anselm Date: Fri, 12 Dec 2025 12:42:15 +0000 Subject: [PATCH 1/3] Fix coverage parsing for projects that use namespaces --- src/PHPUnit/Element.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/PHPUnit/Element.ts b/src/PHPUnit/Element.ts index 307b8524..4d039a5b 100644 --- a/src/PHPUnit/Element.ts +++ b/src/PHPUnit/Element.ts @@ -35,7 +35,11 @@ export class Element { let current = this.node; while (segments.length > 0) { const segment = segments.shift()!; - current = current[segment] ?? undefined; + if (Array.isArray(current)) { + current = current.flatMap((node) => node[segment] ?? undefined).filter((node) => node !== undefined); + } else { + current = current[segment] ?? undefined; + } if (current === undefined) { return []; From 4e5e5aa8b80b1caa29dc0b4a6131ae871957131d Mon Sep 17 00:00:00 2001 From: Edvig Smirnov Date: Mon, 19 Jan 2026 12:13:58 +0100 Subject: [PATCH 2/3] Test: Add unit and integration tests for Element array traversal --- src/PHPUnit/Element.test.ts | 51 ++++++++++++++++++++++++++++++++++ src/PHPUnit/PHPUnitXML.test.ts | 31 +++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 src/PHPUnit/Element.test.ts diff --git a/src/PHPUnit/Element.test.ts b/src/PHPUnit/Element.test.ts new file mode 100644 index 00000000..0e9630d8 --- /dev/null +++ b/src/PHPUnit/Element.test.ts @@ -0,0 +1,51 @@ +import { Element } from './Element'; + +describe('Element', () => { + it('querySelectorAll should traverse arrays', () => { + const data = { + root: { + // 'list' is an array of objects + list: [ + { item: { '@_name': 'A' } }, + { item: { '@_name': 'B' } } + ] + } + }; + + const element = new Element(data); + + // Selector 'root list item' requires traversing 'list' array to find 'item' + const results = element.querySelectorAll('root list item'); + + expect(results.length).toEqual(2); + expect(results[0].getAttribute('name')).toEqual('A'); + expect(results[1].getAttribute('name')).toEqual('B'); + }); + + it('querySelectorAll should handle objects mixed with arrays', () => { + const data = { + coverage: { + project: { + // 'package' is an array here (multi-namespace case) + package: [ + { file: { '@_name': 'file1.php' } }, + { file: { '@_name': 'file2.php' } } + ] + } + } + }; + + const element = new Element(data); + const files = element.querySelectorAll('coverage project package file'); + + expect(files.length).toEqual(2); + expect(files[0].getAttribute('name')).toEqual('file1.php'); + expect(files[1].getAttribute('name')).toEqual('file2.php'); + }); + + it('getAttribute should retrieve values', () => { + const data = { '@_version': '1.0' }; + const element = new Element(data); + expect(element.getAttribute('version')).toEqual('1.0'); + }); +}); diff --git a/src/PHPUnit/PHPUnitXML.test.ts b/src/PHPUnit/PHPUnitXML.test.ts index 6f674c2a..9524f95b 100644 --- a/src/PHPUnit/PHPUnitXML.test.ts +++ b/src/PHPUnit/PHPUnitXML.test.ts @@ -1,6 +1,7 @@ import { join } from 'node:path'; import { URI } from 'vscode-uri'; import { generateXML, phpUnitProject } from './__tests__/utils'; +import { Element } from './Element'; import { PHPUnitXML } from './PHPUnitXML'; describe('PHPUnit XML Test', () => { @@ -327,6 +328,36 @@ describe('PHPUnit XML Test', () => { ]); }); + it('coverage with multiple namespaces (intermediate array)', () => { + // XML where 'package' repeats, creating an array structure in the parser + const xml = generateXML(` + + + + + + + + + + + `); + + // We need to access private method or verify via side effect if getCoverage isn't public. + // Assuming we can test Element traversal via the loaded instance: + const phpUnitXml = new PHPUnitXML(); + phpUnitXml.load(xml, 'phpunit.xml'); + + // Access the underlying element to verify traversal logic in this context + // Note: strict property access might require casting or internal testing + const element = (phpUnitXml as any).element as Element; // Accessing private element for verification + + const files = element.querySelectorAll('phpunit coverage project package file'); + expect(files.length).toEqual(2); + expect(files[0].getAttribute('name')).toEqual('A.php'); + expect(files[1].getAttribute('name')).toEqual('B.php'); + }); + it('load file', async () => { await phpUnitXML.loadFile(phpUnitProject('phpunit.xml')); From 9a327a456c48809a45c58c090e6e25f6a96be60d Mon Sep 17 00:00:00 2001 From: Christoffer Anselm Date: Wed, 21 Jan 2026 14:44:56 +0000 Subject: [PATCH 3/3] Improve tests to actually cover the new code --- src/CloverParser.test.ts | 4 +- src/PHPUnit/Element.test.ts | 55 +++++++++++-------- src/PHPUnit/PHPUnitXML.test.ts | 31 ----------- .../__tests__/fixtures/test1.clover.xml | 3 + 4 files changed, 37 insertions(+), 56 deletions(-) diff --git a/src/CloverParser.test.ts b/src/CloverParser.test.ts index 4314449a..40614a75 100644 --- a/src/CloverParser.test.ts +++ b/src/CloverParser.test.ts @@ -5,8 +5,8 @@ import { CloverParser } from './CloverParser'; describe('CloverParser test', () => { it('parseClover', async () => { const cf = await CloverParser.parseClover('src/PHPUnit/__tests__/fixtures/test1.clover.xml'); - expect(cf.length).toEqual(2); - const dc = cf[0].generateDetailedCoverage(); + expect(cf.length).toEqual(3); + const dc = cf[1].generateDetailedCoverage(); expect(dc.length).toEqual(6); expect(dc[0].executed).toEqual(2); if (dc[0].location instanceof Position) { diff --git a/src/PHPUnit/Element.test.ts b/src/PHPUnit/Element.test.ts index 0e9630d8..6bb35638 100644 --- a/src/PHPUnit/Element.test.ts +++ b/src/PHPUnit/Element.test.ts @@ -1,32 +1,10 @@ import { Element } from './Element'; -describe('Element', () => { +describe('Element Test', () => { it('querySelectorAll should traverse arrays', () => { - const data = { - root: { - // 'list' is an array of objects - list: [ - { item: { '@_name': 'A' } }, - { item: { '@_name': 'B' } } - ] - } - }; - - const element = new Element(data); - - // Selector 'root list item' requires traversing 'list' array to find 'item' - const results = element.querySelectorAll('root list item'); - - expect(results.length).toEqual(2); - expect(results[0].getAttribute('name')).toEqual('A'); - expect(results[1].getAttribute('name')).toEqual('B'); - }); - - it('querySelectorAll should handle objects mixed with arrays', () => { const data = { coverage: { project: { - // 'package' is an array here (multi-namespace case) package: [ { file: { '@_name': 'file1.php' } }, { file: { '@_name': 'file2.php' } } @@ -43,6 +21,37 @@ describe('Element', () => { expect(files[1].getAttribute('name')).toEqual('file2.php'); }); + it('querySelectorAll should handle objects mixed with arrays', () => { + const data = { + coverage: { + project: [ + { file: { '@_name': 'file.php' } }, + { + package: [ + { file: { '@_name': 'file1.php' } }, + ] + }, + { + package: [ + { file: { '@_name': 'file2.php' } } + ] + } + ] + } + }; + + const element = new Element(data); + + const files1 = element.querySelectorAll('coverage project package file'); + expect(files1.length).toEqual(2); + expect(files1[0].getAttribute('name')).toEqual('file1.php'); + expect(files1[1].getAttribute('name')).toEqual('file2.php'); + + const files2 = element.querySelectorAll('coverage project file'); + expect(files2.length).toEqual(1); + expect(files2[0].getAttribute('name')).toEqual('file.php'); + }); + it('getAttribute should retrieve values', () => { const data = { '@_version': '1.0' }; const element = new Element(data); diff --git a/src/PHPUnit/PHPUnitXML.test.ts b/src/PHPUnit/PHPUnitXML.test.ts index 9524f95b..6f674c2a 100644 --- a/src/PHPUnit/PHPUnitXML.test.ts +++ b/src/PHPUnit/PHPUnitXML.test.ts @@ -1,7 +1,6 @@ import { join } from 'node:path'; import { URI } from 'vscode-uri'; import { generateXML, phpUnitProject } from './__tests__/utils'; -import { Element } from './Element'; import { PHPUnitXML } from './PHPUnitXML'; describe('PHPUnit XML Test', () => { @@ -328,36 +327,6 @@ describe('PHPUnit XML Test', () => { ]); }); - it('coverage with multiple namespaces (intermediate array)', () => { - // XML where 'package' repeats, creating an array structure in the parser - const xml = generateXML(` - - - - - - - - - - - `); - - // We need to access private method or verify via side effect if getCoverage isn't public. - // Assuming we can test Element traversal via the loaded instance: - const phpUnitXml = new PHPUnitXML(); - phpUnitXml.load(xml, 'phpunit.xml'); - - // Access the underlying element to verify traversal logic in this context - // Note: strict property access might require casting or internal testing - const element = (phpUnitXml as any).element as Element; // Accessing private element for verification - - const files = element.querySelectorAll('phpunit coverage project package file'); - expect(files.length).toEqual(2); - expect(files[0].getAttribute('name')).toEqual('A.php'); - expect(files[1].getAttribute('name')).toEqual('B.php'); - }); - it('load file', async () => { await phpUnitXML.loadFile(phpUnitProject('phpunit.xml')); diff --git a/src/PHPUnit/__tests__/fixtures/test1.clover.xml b/src/PHPUnit/__tests__/fixtures/test1.clover.xml index da5463bb..9e28f894 100644 --- a/src/PHPUnit/__tests__/fixtures/test1.clover.xml +++ b/src/PHPUnit/__tests__/fixtures/test1.clover.xml @@ -25,6 +25,9 @@ + + +